您的位置 首页 java

并发包中的管程

并发包中的 管程

什么是 lock 和Condition

并发包SDK中存在管程的另一个实现即Lock和Condition,其中Lock可以解决互斥问题,Condition可以解决管程的同步问题(通信和协作)。

读到这里有人肯定马上就有疑问了,用 synchronized 实现的管程完全可以解决互斥和同步问题,为什么要在SDK中重新实现一种管程方式呢?这不是多此一举吗?

没错synchronized确实可以解决互斥和同步问题,但是 存在即合理 ,回答这个问题之前先回顾下死锁问题成立的四大条件。

  • 互斥(同一时间资源只有一个 线程 访问)
  • 持有且等待(持有资源A时尝试获取资源B,尝试等待的时间段内不释放资源A)
  • 不可抢占(持有的资源不能被其它线程抢占)
  • 循环等待(持有资源A获取资源B时会一直等待)

而解决 死锁 问题就是破坏其中一个条件即可,但是互斥条件不能破坏,因为破坏失去加锁的意义,所以可破坏其它三个死锁条件。

  • 破坏持有且等待:一次性申请所有的资源。
  • 破坏循环等待:将资源排序,让资源有序就可以破坏死活。
  • 破坏不可抢占:想要破坏不可抢占条件需要主动释放资源,但如果加锁的是synchronized那么获取不到资源就会进入睡眠,不会响应其它资源。

显然破坏不可抢占条件,synchronized锁并不能做到,所以只能另外设计一把互斥锁来解决死锁问题,那么互斥锁应该存在哪些优点呢?

  • 能够响应中断:synchronized不能破坏不可抢占条件的原因也就是因为线程获取资源A后,去竞争资源B一旦失败就会进入睡眠状态,这时发生死锁将没有机会唤醒线程,如果阻塞的线程能够响应中断,唤醒线程那么就有机会去释放资源A,达到破坏不可抢占的条件。
  • 非阻塞地获取锁:当获取资源失败后,不进入阻塞状态而是立马将结果返回,那么线程就能有机会去释放持有的资源。
  • 支持超时:如果线程在一定时间内没有获取到锁,并不是进入阻塞状态而是返回错误,这样也能有机会释放持有的资源。

Lock如何保证互斥性

显然Lock做到了这一点,能够同时满足上诉的三个优点

  • 支持中断的API
  • void lockInterruptibly () throws Interrupted Exception ;
  • 支持超时API
  • boolean tryLock ( long time , TimeUnit unit ) throws InterruptedException ;
  • 非阻塞获取锁API
  • boolean tryLock ();

Lock如何保证可见性

Lock锁的可见性不能直接应用于JMM(JAVA内存模型)的Happens-Before规则,其锁的可见性规则只是针对synchronized,那么对于上面的代码value能不能保证可见性呢?

  class Test {
    private  final Lock rtl =
   new ReentrantLock();
   int value;
   public void addOne() {
     rtl.lock();  
     try {
       value+=1;
     } finally {
       rtl.unlock();
     }
   }
 }  

答案是肯定的,Lock接口的具体实现ReentrantLock中存在一个被volatile修饰的state变量,简称状态机模式。

  private volatile int state;
 
 protected final int getState() {
     return state;
 }  

加锁部分源码

  // acquires传进来是1
 protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
          if  (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0)
             throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
     }
     return false;
 }  

解锁部分源码

  // releases传进来是1
 protected final boolean tryRelease(int releases) {
     int c = getState() - releases;
     if (Thread.currentThread() != getExclusiveOwnerThread())
         throw new IllegalMonitorStateException();
     boolean free = false;
     if (c == 0) {
         free = true;
         setExclusiveOwnerThread(null);
     }
     setState(c);
     return free;
 }  

简化版本如下

  class SampleLock {
   volatile int state;
   // 加锁
   lock() {
     // 读取state变量
     // 省略代码无数
     state = 1;
   }
   // 解锁
   unlock() {
     // 读取state变量
     // 省略代码无数
     state = 0;
   }
 }  

综上可以简单推出

  • 根据顺序性规则(单线程下前面的操作Happens-Before后面的操作)线程T1 value+=1;Happens-Before于rtl.unlock();
  • 根据valatile规则(对 volatile 变量的写操作Happens-Before变量的读操作)线程T1 unlock();Happens-Before于线程T2 lock();
  • 根据传递性规则(A Happens-Before B,B Happens-Before C,那么A Happens-Before C)得到线程T1 value+=1 Happens-Before 线程T2 lock操作.

所以线程T1对value进行操作,对于线程T2是可见的。

文章来源:智云一二三科技

文章标题:并发包中的管程

文章地址:https://www.zhihuclub.com/200835.shtml

关于作者: 智云科技

热门文章

网站地图