默认的synchronized变量或Lock锁,进入临界的条件可以认为是一个布尔值:
Lock lock= ...;
lock.lock();//一旦获取锁,其他线程就会在这里阻塞,即tryLock() == false
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
这也是绝大多数线程同步代码需要处理的情况。
延伸思考:如果我们需要设计一种同步锁,临界条件不是布尔值而是其他条件,比如是一个数值,它允许这个数值在低于某个值前可以一直允许新的线程进入同步区。
这种需求在涉及某些硬件限制时特别常见,例如每个工作线程需要不定长的一段工作内存,而总工作内存是有限的区间。或者在移动设备上需要限制蓝牙设备的数目来避免触发某些bug。
一种常见的思考是在进入临界区之前判断某个数值:
if(mConditionCount > n) {
//do something
}
synchronized(this) {
......
}
这里需要解决两个问题:用什么方式判断以及如何利用这个数值?(总不能直接空转或者return返回)
如何避免增加额外的锁变量?(大部分稍有并发经验的同学都会想到尽可能只用一个锁解决问题)
下面介绍一种ConditionVariable的思路。ConditionVariable曾经出现在C#及Android的库函数中,但在很多服务端java架构中也有大量类似的应用。
这个类非常简单,并且可以解决以上的两个疑问,它在java中核心写法如下:
private volatile booleanmCondition;
publicvoidopen() {
synchronized(this) {
booleanold = mCondition;
mCondition= true;
if(!old) {
this.notifyAll();
}
}
}
public voidclose() {
synchronized(this) {
while(!mCondition) {
try{
this.wait();
} catch (InterruptedException e) {
}
}
mCondition= false;
}
}
public voidblock() {
synchronized(this) {
while(!mCondition) {
try{
this.wait();
}
catch (InterruptedException e) {
}
}
}
}
比一般的lock对象多了一个方法。它将同步临界条件拆解为三个方法:open(),block(),close()。block()尝试获取ConditionVariable的锁状态,若失败则阻塞。
close()令ConditionVariable上锁。一般block和close是连用的。
open()解除该ConditionVariable的锁状态。
它的典型使用代码是:
mConditionVariable.block();
mConditionVariable.close();
//临界区代码
mConditionVariable.open();
相比一般的Lock使用,容易看出这个类设计特别的地方在于:一个ConditionVariable实例实质是一个锁变量。
使用一个volatile的boolean变量mCondition来作为该锁的辅助修饰。
一个线程每次尝试获取锁并成功后,会跳过while循环进入后面的临界区代码。
若一个线程获取锁失败,此线程直接进入while循环把自己挂起。(此时这个锁的lock行为从阻塞变为直接挂起)
调用此锁的open()时所有线程走正常的notifyAll逻辑。
那么有些同学会有疑问:这个mCondition的辅助判断是否是线程安全的?通过检查代码发现所有对于mCondition的访问都要保持一个原则:这些访问应位于synchronized (this)的代码块内。此举可保障一切对于mCondition的操作都是队列化的。(如果你自己要从外部操作mCondition也是同理)
此时我们上面的延伸思考问题也就容易考虑了:
只需要把mCondition改为自己需要的变量类型即可。是否需要改为CAS?这个小问题可以自行思考。
//注意这种实现的锁是不可重入锁,有兴趣的同学可以自行思考如何改造为可重入锁
private static final int MAX_SYNC = 4;
private volatile intmCondition;
publicvoidopen() {
synchronized (this) {
intold = mCondition;
mCondition--;
if(old == 0) {
this.notifyAll();
}
}
}
public voidclose() {
synchronized(this) {
while(mCondition>= MAX_SYNC) {
try{
this.wait();
}
catch(InterruptedException e) {
}
}
mCondition++;
}
}
publicvoidblock() {
synchronized (this) {
while(mCondition>= MAX_SYNC) {
try{
this.wait();
}
catch(InterruptedException e) {
}
}
}
}
另外有个小问题,某些中文帖子里ConditionVariable.java的代码是有并发bug的。具体是什么bug对比代码一看便知,这里不再赘述。
对于本文代码有误或技术认知不深刻的地方欢迎指出。