线程同步三种方式
重入锁ReentrantLock代码块同步:
Lock mLock = new ReentrantLock();
mLock.lock();
try{
//do something
} finally{
mLock.unlock();
}
这种同步确保了同一时间只能有一个任务访问的代码区。值得注意的是一定要把解锁的操作放在finally,这样代码区发生异常时锁能释放掉,否则其他线程将永远被阻塞。
一般来说Lock
会配合Condition
一起使用。调用Condition.await()
方法会阻塞该线程,调用Condition.signalALL()
将会重新激活所有等待的线程。
synchronized同步
对于这种同步大家可能比较熟悉,synchronized同步分为方法同步和代码块同步。
方法同步:
public synchronized void method() {
...
}
此同步方法等同于上方Lock的同步代码块结构,不过这种方法更加简洁。
代码块同步:
synchronized(Object) {
...
}
其获得了Object的锁,Object指的是一个对象。同步代码块是非常脆弱的,通常不推荐使用。
volatile关键字同步
由于内存模型以及并发编程中的三个特性:
原子性、可见性和有序性
,而volatitle
(详解)无法保证操作的原子性。所以,使用volatile
必须具备以下两个条件:
1. 对变量的写操作不会依赖于当前值。
2. 改变量没有包含在具有其他变量的不变式中。
这里我们使用两种场景来举例说明
- 状态标志
volatile boolean shutdownRequested;
...
public void shutdown(){
shutdownRequested = true;
}
public void doWork() {
while(!shutdownRequested) {
...
}
}
- 双层检查模式(DCL)
public class Signleton {
private volatile static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized)(this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
小结
与锁相比,
volatile
变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性,但在通常情况下我们最好还是使用synchronized。