定义
根据各线程访问数据的次序, 可能产生讹误的对象. 该情况通常称为竞争条件(race condition), 同步存取可以避免多线程引发的对共享数据的讹误.
两种机制防止代码块受并发干扰, 由JUC框架提供独立的类
synchronized
关键字- 1.5引入的
java.util.concurrent.locks.*
, SE11文档
锁对象
基本结构
myLock.lock();
// 不能使用带资源的try,
// 1.解锁方法不是close 2.带资源try首部声明新变量
try {
// critical section
// 如果因异常直接跳出临界区会使对象处于受损状态
} finally {
// 必须释放
myLock.unlock();
}
复制代码
锁是可重入的, 因为每次调用lock, 都会调用unlock来释放, 所以可以对lock方法嵌套调用. lock对象持有锁计数根据嵌套增加, 当全部释放后计数为0
通常, 可能要保护需若干个操作来更新或检查共享对象的代码块. 要保证这些操作完成后, 另一个对象才能使用相同对象
ReentrantLock(boolean fair)
, 公平策略的锁比常规锁慢很多, 且无法保证调度器不会忽略一个为该锁等待很长时间的线程.
条件对象(条件变量conditional variable)
使用条件对象来管理那些已经获得锁但却不能做有用工作的线程.
一个锁对象可以有一个或多个相关的条件对象lockObj.newCondition()
, 当条件不满足时调用conditionObj.await()
当前线程阻塞, 并放弃锁
等待获得锁的线程 和 调用await()
的线程 存在本质不同
- 当一个线程调用
await()
, 它进入该条件的等待集, 当锁可用时, 该线程不能马上解除阻塞, 相反它处于阻塞状态, 直到另一个线程调用同条件上的signalAll()
为止. signalAll()
将该条件下等待集中的线程全部移出, 它们成为可运行的, 调度器将再次激活它们, 一旦锁可用, 其中某个将从await()
调用返回, 获得该锁并从被阻塞的地方继续执行.- 因为
signalAll()
只是通知等待线程条件可能已经满足, 所以需要再次检测条件// 常用写法 while(!(/*ok to proceed*/)) condition.await(); 复制代码
在对象的状态有利于等待线程的方向改变时调用signalAll()
锁和条件总结
- 锁用来保护代码片段, 任何时刻只能有一个线程执行被保护代码
- 锁可用管理试图进入被保护代码的线程
- 锁可以拥有一个或多个相关的条件对象
- 每个条件对象管理那些已进入被保护代码片段但还不能运行的线程
synchronized
Java中每个对象都有一个内部锁, 可重入
内部对象锁只有一个相关条件即内部条件, wait()
添加线程到等待集, notifyAll()/notify()
解除等待线程的阻塞状态, 如果这几个方法执行时当前线程不是锁的持有者抛出IllegalMonitorSateException
异常
对静态方法加synchronized
关键字, 说明该方法被调用时, 该类的Class
对象的锁被锁住, 没有其他线程调用同一个方法或其他任何同步静态方法
局限性
- 不能中断一个正在试图获得锁的线程
- 试图获得锁时不能设定超时
- 每个锁仅有单一条件
选择Lock/Condition还是使用synchronized关键字?
最好都不使用, 而是使用JUC中的阻塞队列
如果synchronized
适合, 尽量选择它, 因为简单明了减少出错. 要使用Lock/Condition特性时才选择它
同步阻塞
Object obj = new Object();
synchronized(obj) {
//不能保证调用obj的某个方法是同步方法
}
复制代码
又称客户端锁定, 不推荐使用
监视器概念
synchronized
关键字之前, 锁和条件并不是严格面向对象, 便有了监视器的概念. 后该概念被不不精确的方式设计为每个对象有一个内部锁和内部条件, 用synchronized
声明方法,该方法就像监视器方法一样.
Volatile域
- 多处理器计算机可以暂时保存内存中的值寄存器或本地缓冲区, 导致不同处理器上的线程在同一位置取到不同值
- 编译器可以改变指令执行顺序以使吞吐量最大化, 编译器假定内存的值仅在代码有显式修改指令才改变, 但该值却有可能被另一线程改变
被volatile修饰的实例域, 编译器和虚拟机知道该域可能被另一线程并发更新, 可以保证可见性, 有序性不能保证原子性.
final变量
final
可以实现安全地访问一个共享域, 其他线程会在构造函数完成构造后看到final
变量, 且不可修改
死锁
所有线程都陷入阻塞. Java语言没有任何东西可以避免或打破死锁, 必须仔细设计程序以确保不会发生