Java并发编程杂记(1)
高并发:
cpu — 缓存 — 内存
资源利用率
公平性
便利性
生活举例 — 串行任务中的异步性:我在烧水的时候看书 — 平衡点
安全性问题 — 产生竞态条件
共享数据 — 共享相同的内存地址,并且并行执行
不可破坏 — 永远不要发生最糟糕的事情
活跃性问题 — 某个正确的事情最终会发生
某个操作无法执行下去
性能问题 — 频繁的上下文切换,CPU花更多的时间在线程调度上
编写安全的多线程代码,核心在于对状态访问操作进行管理
共享(Shared):可被多线程访问
可变(Mutable) :在对象生命周期内可发生变化
对象状态,存储在状态变量(实例或静态域)中的数据,对象状态还可能依赖其他对象域
在程序访问对象的角度看这个问题
一个对象是否线程安全,取决于它是否被多线程访问。使用同步机制保证线程安全
需使用同步机制的情况:
当多线程访问一个状态变量,并且其中有一个线程进行写入操作时,必须采用同步机制协同线程对变量的访问
Java的同步机制
synchronized — 重量级,独占加锁。
volatile类型变量
显式锁
原子变量
当多线程访问状态变量时,没有采取适合的同步机制则:
不在线程之间共享该变量
将状态变量改为不可变的变量
在访问状态变量时使用同步
线程安全类
线程安全程序
线程安全的程序是否完全由线程安全类构成?
完全由线程安全类构成的程序不一定线程安全。线程安全类中也可以包含非线程安全的类
线程安全性
多线程下的正确性 — 当多线程下访问某个类,这个类始终都能保证正确的行为
单线程下的正确性 — 所见即所得
无状态对象一定是线程安全的
原子性问题,修改变量时,实际可能并不是原子操作(读取—修改—写入),多线程下可能由于时序问题产生的数据不一致—–竞态条件 — 安全性问题
先检查后执行 — 通过一个可能失效的结果决定下一步动作
e.g A去地点Q的星巴克与B会面,到达Q之后发现有两家星巴克分别叫S1,S2.在S1没有看到B,然后去到S2也没有看到B,B迟到,B在A离开S1后到了S1,B到达S2但是B去S1找A.
延迟加载的竞态条件
竞态条件和数据竞争(Data Race)
数据竞争:在访问共享变量的非final类型的域时没有采用同步进行协同,那么就会出现数据竞争。当一个线程写入一个变量,另一个线程读取这个变量,或者读取一个之前由另一个线程写入的变量时,
并且这两个线程直接没有使用同步,就会出现数据竞争。不是所有竞态条件都是数据竞争,同样并非所有数据竞争都是竞态条件,但两者都会导致并发程序失败。
如何保证原子性:
复合操作保证原子性
原子操作:对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作
待解决的小功能 — 实现一个因式分解 (缓存上一次计算的结果)
保存状态一致(一致性),需要带单个原子操作中更新所有相关的状态变量
内置锁(监视器锁)— 是一个互斥锁,即A尝试获取由B线程持有的锁时,A会阻塞,且等到B释放锁后A才能重新获得锁
synchronized(lock){
}
作为所的对象引用
这个锁保护的代码块
重入
内置锁是可重入的,当一个线程试图获取它已经持有的锁时,这个请求会成功。其他线程尝试获取会阻塞unt 。获取锁的粒度是线程,而不是调用。
countNum = 0 –没有线程持有锁
countNum = 1 — 当前有一个线程持有锁
重入CountNum++
在释放时,会依次递减 — 直到为0
用锁保护状态
对于可能被多个线程同时访问的可变状态变量,在访问它时,都需要持有同一把锁 —- 这个变量由这个所保护
对于每个包含多个变量的不变性条件,其中所涉及到的变量都需要同一把锁来保护
synchronized可保证单个操作的原子性,但是保证不了符和操作的原子性
当执行较长时间计算,可能无法快速完成操作时,一定不要持有锁
版权声明:本文为DaveMo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。