多线程 -- 同步
同步的代码和其他普通代码, 主要有2个区别, 如下
原子性 atomicity
原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。
可见性 visibility
可见性则更为微妙,它要对付内存缓存和编译器优化的各种反常行为。它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 。
作用:如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
原理:当对象获取锁时,它首先使自己的高速缓存无效,这样就可以保证直接从主内存中装入变量。 同样,在对象释放锁之前,它会刷新其高速缓存,强制使已做的任何更改都出现在主内存中。 这样,会保证在同一个锁上同步的两个线程看到在 synchronized 块内修改的变量的相同值。
Volatile 只有可见性, 没有原子性
锁对象
Lock用于保证线程安全, 确保任何时刻, 只有一个线程进入临界区;
使用Lock保护的代码块基本如下:
lock.lock();
try {
// some code
}finally {
lock.unlock();
}
上面代码结构保证同一时刻,只有一个线程运行临界区的代码; 一旦一个线程持有了锁对象, 其他任何线程都无法通过lock语句; 其他线程调用lock时, 都会被阻塞, 直到上一线程释放锁对象;
ReentrantLock (重入锁)
锁是可重入的: 线程可以重复获取已经持有的锁; 锁有一个持有计数,用来跟踪lock方法的嵌套调用; 线程在每一次调用lock后都要调用unlock来释放锁;
ReadWriteLock (读写锁)
多个线程对一个数据读取,而很少线程修改其数据时适用;
读写锁允许线程共享读取访问, 但是修改数据的线程互斥访问;
- readLock : 允许多个线程公用读锁, 但是排斥所有的写操作
- writeLock : 排斥所有的读操作和写操作
Condition (条件对象)
线程进入临界区后, 却发现要满足条件, 才能继续执行, 这时需要释放锁(不然就死锁了), 等待满足条件时, 接着执行;
条件对象, 即用来解决上面的问题, 用于去管理已经获取锁对象,去不能做有用工作的线程;
通常示例如下:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while ( isSupport ){
// 释放lock, 同时阻塞该线程, 等待条件满足
condition.await();
}
// some code
}finally {
lock.unlock();
}
一个锁对象可以有多个条件对象, 可以用newCondition
方法获取一个条件对象;
不满足条件时调用Condition.await
方法释放锁, 同时当前线程会被阻塞; 当一个线程调用await方法后, 该线程就没办法重新激活自身, 需等待其他线程通知;
其他线程运行后, 调用Condition.signalAll
方法, 通知所有阻塞等待的线程解除阻塞; 原先被阻塞的进程再次竞争, 获取锁对象, 继续访问;
被解除阻塞的线程, 被唤醒后, 可能仍不满足条件, 所以一般使用 while 语句, 判断条件是否满足;
小结
- 锁用来保护代码片段, 任何时刻只能有一个线程执行被保护的代码;
- 锁可以拥有一个或多个相关的条件对象;
- 每个条件对象管理那些已经进入被保护的代码段却还不能运行的线程;
synchronized & 对象锁
每一个Java对象, 都有一个内部锁;
如果方法使用synchronized关键字, 那么对象锁将会保护整个方法; 即要调用该方法, 线程先必须获取该对象的对象锁;
对象的内部锁,只有一个条件对象; 使用方法就是Object的 wait, notifyAll 方法;
使用内部锁, 除了在方法上加上synchronized关键字, 还可以使用synchronized(obj){...}
代码段
Volatile
仅仅为了读写一个对象实例, 就使用同步, 开销比较大;
Volatile关键字, 为实例域的同步访问, 提供了一种免锁机制;
volatile不能提供原子性; 所以一般用于基本类型的变量 和null判断
参考链接:
http://blog.csdn.net/vking_wang/article/details/9952063
网友评论