同步问题中,一个很重要的问题是同步的域,什么是同步的域呢?简单以 synchronized 这个关键字来说,就是它所同步的范围。并发编程中很多时候出现的问题没有选好同步范围所导致的。但现有的同步关键字synchronized所能体现出来的对域的控制,估计用过的的人都说不是很理想。这个时候是不是很怀念Windows下所提供的Mutex操作。
现在为了方便我们自己也可以做一个类似Windows的Mutex的类来方便我们对域的控制,下面看这个类的制作过程。
和制作上一个类一样,我们先来说一下希望这个类完成的功能:
1.调用getMutexFlag(),仅能是并发的线程中的一个继续执行,其余的阻塞。
2.调用freeMutexFlag(),执行的线程离开同步块(临界区),并能从阻塞线程中释放一个线程从getMutexFlag()中返回。
3.getMutexFlag()、freeMutexFlag(),都必须是原子操作。
上面这三个条件其实就是对Windows下Mutex的要求。
已开始我们也许会写出这样的代码:
1 package com.choi; 2 3 public class MutexFlag { 4 5 protected Thread currentThread = null; 6 7 public synchronized void getMutexFalg() { 8 if (currentThread == null) { 9 currentThread = Thread.currentThread(); 10 } 11 while (currentThread != Thread.currentThread()) { 12 try { 13 wait(); 14 if (currentThread == null) { 15 currentThread = Thread.currentThread(); 16 } 17 } catch (Exception e) { 18 } 19 } 20 } 21 22 public synchronized void freeMutexFlag(){ 23 if(currentThread!=null){ 24 currentThread = null; 25 try { 26 notify(); 27 } catch (Exception e) { 28 } 29 } 30 } 31 }
这段代码看起来是没有问题的,其实这段代码运行起来确实是没有问题的,但我们能把它提供给我们的项目组用吗?答案是不能的,我们来考虑一种情况:
项目组有三个程序员A,B,C A用这个类写了一个函数(get到Mutex未释放),B用这个类写了一个函数(get到Mutex未释放),C调用了A和B的函数,用在一个线程中了。则这个线程运行到A时得到了Mutex,运行到B时再去申请Mutex能申请到吗?看代码,回答是NO。并申请不到,因为第一个还没有释放。Java里面如果是两个synchronized嵌套,则没有问题,第一个得到锁后,第二个如申请的是同一个锁,就会自动判断为已申请到。
我们可以用一个变量来是我们的MutexFlag实现synchronized的嵌套解决方案:
1 package com.choi; 2 3 public class MutexFlag { 4 5 protected Thread currentThread = null; 6 protected int count = 0; 7 8 public synchronized void getMutexFalg() { 9 while (tryGetMutexFlag() == false) { 10 try { 11 wait(); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 18 public synchronized boolean tryGetMutexFlag() { 19 if (currentThread == null) { 20 currentThread = Thread.currentThread(); 21 count = 1; 22 return true; 23 } 24 if (currentThread == Thread.currentThread()) { 25 count++; 26 return true; 27 } 28 return false; 29 } 30 31 public synchronized void freeMutexFlag() { 32 if (currentThread == Thread.currentThread()) { 33 count--; 34 } 35 if (count == 0) { 36 currentThread = null; 37 notify(); 38 } 39 } 40 }
为了编写方便我把判断获取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