同步问题中,一个很重要的问题是同步的域,什么是同步的域呢?简单以 synchronized 这个关键字来说,就是它所同步的范围。并发编程中很多时候出现的问题没有选好同步范围所导致的。但现有的同步关键字synchronized所能体现出来的对域的控制,估计用过的的人都应该感觉到并不是很理想。这个时候是不是很怀念Windows下所提供的Mutex操作,通过申请和释放的函数的位置控制同步的域,用起来要方便一些。
现在为了方便我们自己也可以做一个类似Windows的Mutex的类来方便我们对域的控制,下面看这个类的制作过程。
和制作上一个类一样,我们先来说一下希望这个类完成的功能:
1.调用getMutexFlag(),仅能是并发的线程中的一个继续执行,其余的阻塞。
2.调用freeMutexFlag(),执行的线程离开同步块(临界区),并能从阻塞线程中释放一个线程从getMutexFlag()中返回。
3.getMutexFlag()、freeMutexFlag(),都必须是原子操作。
上面这三个条件其实就是对Windows下Mutex的要求。
一开始我们也许会写出这样的代码:
package com.choi; public class MutexFlag { protected Thread currentThread = null; public synchronized void getMutexFalg() {
if (currentThread == null) {
currentThread = Thread.currentThread();
}
while (currentThread != Thread.currentThread()) {
try {
wait();
if (currentThread == null) {
currentThread = Thread.currentThread();
}
} catch (Exception e) {
}
}
} public synchronized void freeMutexFlag(){
if(currentThread!=null){
currentThread = null;
try {
notify();
} catch (Exception e) {
}
}
}
}
这段代码看起来是没有问题的,其实这段代码运行起来确实是没有问题的,但我们能把它提供给我们的项目组用吗?答案是不能的,我们来考虑一种情况:
项目组有三个程序员A,B,C。 A用这个类写了一个函数funA(get到Mutex未释放),B用这个类写了一个函数funB(get到Mutex未释放),C调用了funA,funB,用在一个线程中了。则这个线程运行到funA时得到了Mutex,运行到funB时再去申请Mutex能申请到吗?通过分析代码执行,肯定是NO。并申请不到,因为第一个还没有释放。Java里面如果是两个synchronized嵌套,则没有问题,第一个得到锁后,第二个如申请的是同一个锁,就会自动判断为已申请到。
我们可以用一个变量来是我们的MutexFlag实现synchronized的嵌套解决方案:
package com.choi; public class MutexFlag { protected Thread currentThread = null;
protected int count = ; public synchronized void getMutexFalg() {
while (tryGetMutexFlag() == false) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public synchronized boolean tryGetMutexFlag() {
if (currentThread == null) {
currentThread = Thread.currentThread();
count = ;
return true;
}
if (currentThread == Thread.currentThread()) {
count++;
return true;
}
return false;
} public synchronized void freeMutexFlag() {
if (currentThread == Thread.currentThread()) {
count--;
}
if (count == ) {
currentThread = null;
notify();
}
}
}
为了编写方便我把判断获取MutexFlag的代码写到了同步的tryGetMutexFlag中了,没申请成功一次就对count做加一操作,每一free都对count做减一操作,那么当count从新等于零的时候我们就去释放一个线程获取MutexFlag。
哦,有人说如果没有调用get就调用free会有问题,这里说明一下没有问题的,因为如果没有get,currentThread是等于null的,所以count==0,然后就会调用notify,其实没有问题啦,测试发现,notify不会抛出人异常,相当于空操作。
欢迎到我的GitHub主页获取代码:https://github.com/choitony/Java-MutexFlag-Class/tree/master