leetcode1116 多信号量嵌套控制下的DCL检查

  先吐槽一下网上对原子性的解释,总是说原子性是不可分割的。

  那只是在单核心下的语义,单核心下不可分割的操作意味着执行过程中不会有其它线程执行,从而导致变量污染,也就是原子性操作涉及的共享变量是安全的。

  所以多线程下原子性的语义也应该是 :原子性操作涉及的共享变量是安全的,不会有其它线程修改。

  也就是说保证原子性,是保证“某段代码”以及“对所有这段代码中牵扯到的共享变量的操作”是线程排他的。

  在单线程环境下存在数据依赖或者控制依赖的一段指令,多线程下我们必须保证其原子性。原因很简单,如果它们不是原子的,在它们执行的间隙,其它线程改变共享变量的值会导致变量污染。

  但当外部控制是信号量而不是锁时,无法保证内部逻辑的原子性。所以我们进行完一系列控制后,在真正执行任务前需要返回来判断外部控制是否还是有效的,并在内部控制保证任务的原子性,这便是DCL的思路。

 

 

  题目如上,题目不难,是一个通过信号量通信的典型例子。

  题目中几个点比较具有代表意义:

  1. 对于共享变量 n 的累减与 current 的累加是一个典型的基于共享变量当前值计算下一步值的操作,通常情况下是一个读操作与一个写操作的组合。因为这两个操作在单线程环境下存在数据依赖,所以在多线程情况下我们应该保证其原子性,防止在单线程的两个操作之间,其它线程修改了共享变量的值造成变量污染。

  但是在本题中,每个线程需要自己的信号量放行后才可以执行共享变量 n 的累减与 current 的累加操作,且同一时间只有一个信号量是 true ,所以我们在保证线程对信号量的访问正确性的情况下,信号量等于是共享变量 n 的累减与 current 的累加的锁,同一时间只有一个线程可以执行,天然的保证了其原子性。

  2. n 与 三个信号量共同构成了线程执行任务的条件。

  当我们遇到类似的结构时:

  while( 条件1 ){

    while( 条件2 ){

      执行任务;

    }

  }

  任务的执行需要多个共享变量放行,我们需要尤其注意。

  在满足了外层条件并等待在内层条件满足的时间段中,外层条件很可能被其它线程改变。因此在满足内层条件后我们一定要对外层条件再进行一次检查。除非内层条件的等待与外层条件的修改是互斥的,但代价非常大。因为我们要互斥的,不是 条件1 的写操作与 条件2 的读操作,而是 条件1 的写操作与等待条件2直到其为真。

  比如:

int lock1=10;
int lock2=0;
private synchronized void setLock1(int i){
    lock1=i;  
}
while(lock1!=0){
    synchronized(this){
        while(lock2==0){
            等待;
        }
开启lock3; } }

  使 lock1 的写操作与等待在 lock2 上的所有操作共用一把锁。逻辑上很合理,一个线程基于 lock1==1 做事,那么我做完之前,谁都不允许去修改 lock1 。

  如果说,所有线程都只使用这两把锁,这样做没有什么问题。但如果 lock2 控制的任务牵扯到与其它线程的协作的话,情况就复杂了起来。

  比如此时还有一个线程:

while(lock1!=0){
    synchronized(this){
        while(lock3==0){
            等待;
        }
开启 lock2;
} }

  需要锁的顺序为: lock1-->lock3-->unlock2 ,而上一个线程的任务需要锁的顺序为:lock1-->lock2-->unlock3。

  lock3开启lock2关闭的情况下,第一个线程先获取lock1,则整个程序便死锁了,因此我们需要更细粒度的互斥关系。

  lock1 不应该是锁,而应该是信号量,它代表的是线程可用资源而不是保证线程互斥,那么多个线程可以进入 lock1 控制的区域。但是这样依然不够,lock3-->unlock2 与

lock2-->unlock3 存在循环依赖,依然会造成死锁。所以我们必须让进入 lock2 与进入 lock3 也是互斥的。

  而在 lock1 控制的区域内部,执行 lock2 加锁区域 与 执行 lock3 加锁区域是互斥的,而且只有在这两个区域内可以进行 lock1 的修改操作,那么不管进入 lock2 还是进入 lock3,再进行 lock1 的取值,其结果都将是线程安全的。

class ZeroEvenOdd {
   //待打印数据个数
private volatile int n;
   //打印 0 的信号量
private volatile boolean isZero=true;
   //打印 偶数 的信号量
private volatile boolean isEven=false;
   //打印 奇数 的信号量
private volatile boolean isOdd=false;
   //当前打印到的数
private volatile int current=0; public ZeroEvenOdd(int n) { this.n = n; } // printNumber.accept(x) outputs "x", where x is an integer. public final void zero(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isZero){ if(n==0){ return; } Thread.yield(); } if(n==0){ break; } printNumber.accept(0); isZero=false; if((current&1)==0){ isOdd=true; }else{ isEven=true; } } } public final void even(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isEven){ if(n==0){ return; } Thread.yield(); } current=current+1; printNumber.accept(current); n--; isEven=false; isZero=true; } } public final void odd(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isOdd){ if(n==0){ return; } Thread.yield(); } current=current+1; printNumber.accept(current); n--; isOdd=false; isZero=true; } } }

 

posted @ 2020-03-08 01:00  牛有肉  阅读(398)  评论(0编辑  收藏  举报