线程同步ReentrantLock,condition(await,signal)

来源:互联网 发布:开心农场php源码 编辑:程序博客网 时间:2024/05/18 16:18

一.synchronized和ReentrantLock 故事

1. 我们前面(二)java 的线程同步(synchronized ,wait,notify)讲了synchronized的同步方法,java 就是这么神奇,这里又有一个同步的方法!当然,长江后浪推前浪,后者肯定比前者是由优越之处的。

先看看 synchronized 的一些限制: 
     1.1 :无法中断正在等候获取一个锁的线程 
     1.2 :无法通过投票得到一个锁 
     1.3 :释放锁的操作只能与获得锁所在的代码块中进行,无法在别的代码块中释放锁 。

 

 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票定时锁等候可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

定时锁等候:设置定时等候之后,在这个等候时间内如果没有获得这个锁,这个线程就会自己中断。

可中断锁等候:就是线程等候可以自己中断也可以别人中断。

锁投票:这个不太懂,有懂的大牛给提示一下,我到时引用到博文里面来(会注明作者的)。

 

二.ReentrantLock 的简单用法:

       1.lock() 和unlock():ReentrantLock手动获取和释放锁,一般放在try catch 块中。如下所示

Java代码  收藏代码
  1. class X {  
  2.    private final ReentrantLock lock = new ReentrantLock();  
  3.    // ...  
  4.    
  5.    public void m() {  
  6.      lock.lock();  // block until condition holds  
  7.      try {  
  8.        // ... 同步的代码  
  9.      } finally {  
  10.        lock.unlock()  
  11.      }  
  12.    }  
  13.  }  

 

    2.既然有锁,那多个线程下面的阻塞注明做到呢?这就需要用到Condiction类了,这个类的作用就相当于给ReentrantLock加类似synchronized的wait,notify方法,不过它更强大!它的await可以有条件的等待,比如定时等待等用法,为方便比较,我结合前面synchronized的例子改一下,例子如下所示:

 

Java代码  收藏代码
  1. package com.hxw.Threads;  
  2.   
  3. import java.util.concurrent.locks.Condition;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6. public class ProducerConsumeGameLock {  
  7.   
  8.     /** 
  9.      * @param args 
  10.      */  
  11.     public static void main(String[] args) {  
  12.         Bucket bucket = new Bucket(); // 构造篮子  
  13.         new Thread(new Producer(bucket),"Producer线程").start();  
  14.         new Thread(new Consumer(bucket),"Consumer线程").start();  
  15.     }  
  16.   
  17. }  
  18.   
  19. final class Consumer implements Runnable {  
  20.     private Bucket bucket;  
  21.   
  22.     public Consumer(Bucket bucket) {  
  23.         this.bucket = bucket;  
  24.     }  
  25.   
  26.     @Override  
  27.     public void run() {  
  28.         for (int i = 0; i < 10; i++) {  
  29.             bucket.get();  
  30.         }  
  31.     }  
  32.   
  33. }  
  34.   
  35. final class Producer implements Runnable {  
  36.     private Bucket bucket;  
  37.   
  38.     public Producer(Bucket bucket) {  
  39.         this.bucket = bucket;  
  40.     }  
  41.   
  42.     @Override  
  43.     public void run() {  
  44.         for (int i = 0; i < 10; i++) { // 来回十次交易  
  45.             bucket.put((int) (Math.random() * 1000));  
  46.         }  
  47.     }  
  48. }  
  49.   
  50. class Bucket {  
  51.     private volatile int packOdBalls;  
  52.     private volatile boolean available = false;  
  53.     private final ReentrantLock lock = new ReentrantLock();  
  54.     private Condition noBull = lock.newCondition();  
  55.     private Condition fullBull = lock.newCondition();  
  56.   
  57.     public int get() { // 消费者从篮子里面取出球  
  58.         lock.lock();  
  59.         try {  
  60.             while (available == false) { // 如果没有就等着,但是为什么不是if呢?后面会做解析  
  61.                 System.out.println("消费者:暂时没有球可以消费我就等着...");  
  62.                 noBull.await(); // wait();  
  63.             }  
  64.             System.out.println("消费者获得了" + packOdBalls + "个球");  
  65.             available = false;  
  66.             System.out.println("持有消费线程数: "+lock.getHoldCount());  
  67.             fullBull.signal(); // notify();  
  68.         } catch (InterruptedException e) {  
  69.             e.printStackTrace();  
  70.         } finally {  
  71.             lock.unlock();  
  72.         }  
  73.         // 有的话就取出来  
  74.         return packOdBalls;  
  75.     }  
  76.   
  77.     public synchronized void put(int packOdBalls) { // 生产者将生产球并放入到篮子里面  
  78.         lock.lock();  
  79.         try {  
  80.             while (available) {  
  81.                 System.out.println("生产者:既然篮子里面已经有球了我就消费完了再生产吧!");  
  82.                 fullBull.await(); // wait();  
  83.             }  
  84.             this.packOdBalls = packOdBalls;  
  85.             available = true;  
  86.             System.out.println("生产者放进去了" + packOdBalls + "个球");  
  87.             noBull.signal(); // notify();  
  88.         } catch (InterruptedException e) {  
  89.             e.printStackTrace();  
  90.         } finally {  
  91.             lock.unlock();  
  92.         }  
  93.   
  94.     }  
  95.   
  96. }  

 

【运行结果与前面一样,需要看的可以戳文章头的链接】

 

三.ReentrantLock的方法…

  1. public boolean tryLock():

仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 这个方法会完全忽略公平策略,理解trylock的意思就知道了,就是“努力试着去锁”,所以一旦这个锁没有线程持有,他就抢占了!

2. public boolean tryLock(long timeout,TimeUnit unit):

如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。说的详细点如果超出了指定的等待时间,则返回值为 false。如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。如果为了使用公平的排序策略,已经设置此锁,并且其他线程都在等待该锁,则不会 获取一个可用的锁。这与 tryLock() 方法相反。如果想使用一个允许闯入公平锁的定时 tryLock,那么可以将定时形式和不定时形式组合在一起:

if (lock.tryLock() || lock.tryLock(timeout, unit) ) { ... }

注意:有的人可能不理解这两个参数,其实就是前面是数词,后面是量词(SECOND,HOUR等等)

3. public int getHoldCount()查询当前线程保持此锁的次数。

4. public boolean isLocked()查询此锁是否由任意线程保持。

等等还有很多方法,这些查询方法都只能叫监控方法,因为线程无时无刻不在变化,可能返回这个值的下一纳秒,这个值就变了

5.还有一个构造方法:

public ReentrantLock(boolean fair)

创建一个具有给定公平策略的 ReentrantLock。

 

四.Condition方法一览…

1.await():

让当前线程一直处于等待状态,直到唤醒或中断。需要注意的是,这里说的是当前线程,这个“当前”指的是这个await所属Condition对象的线程创建者,这一点从上面的源码也可以看出(我们每个线程都new了一个Condition)。

对于唤醒,这里有个虚假唤醒,虚假唤醒也会引起当前线程唤醒,我们后面来讨论

2boolean await(long time,TimeUnit unit):

让当前线程在指定时间内一直处于等待状态,直到被唤醒或中断。

3void signal():唤醒一个等待线程。这个与notify功能类似

4. void signalAll():唤醒所有等待线程。这个与notifyAll功能类似

 

五.虚假唤醒

虚假唤醒简而言之就是一个signal()可能唤醒了多个线程,前面代码第60行左右提到过。当然这个概率是很低的,你会发现,你把while改成if也是可以运行的。但是我们也要避免这种情况,所以我们就改成了while,while()不仅仅在等待条件变量前检查条件变量,实际上在等待条件变量后也检查条件变量。

 

这样对condition进行多做一次判断,即可避免“虚假唤醒”.


一、Condition 类

  在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.concurrent.locks.ReentrantLock 锁,JDK也为我们提供了与此功能相应的类java.util.concurrent.locks.Condition。Condition与重入锁是通过lock.newCondition()方法产生一个与当前重入锁绑定的Condtion实例,我们通知该实例来控制线程的等待与通知。该接口的所有方法:

复制代码
public interface Condition {     //使当前线程加入 await() 等待队列中,并释放当锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似。    void await() throws InterruptedException;    //调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。    //调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。     void awaitUninterruptibly();    // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。    //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;    //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。     long awaitNanos(long nanosTimeout) throws InterruptedException;    //与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。    boolean await(long time, TimeUnit unit) throws InterruptedException;   //适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。    boolean awaitUntil(Date deadline) throws InterruptedException;        //唤醒一个在 await()等待队列中的线程。与Object.notify()相似    void signal();   //唤醒 await()等待队列中所有的线程。与object.notifyAll()相似    void signalAll();}
复制代码

二、使用

1、await()  等待  与 singnal()通知

复制代码
 1 package com.jalja.org.base.Thread; 2  3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Condition; 5 import java.util.concurrent.locks.ReentrantLock; 6  7 /** 8  * Condition 配合Lock  实现线程的等待 与通知 9  */10 public class ConditionTest{11     public static ReentrantLock lock=new ReentrantLock();12     public static Condition condition =lock.newCondition();13     public static void main(String[] args) {14         new Thread(){15             @Override16             public void run() {17                 lock.lock();//请求锁18                 try{19                     System.out.println(Thread.currentThread().getName()+"==》进入等待");20                     condition.await();//设置当前线程进入等待21                 }catch (InterruptedException e) {22                     e.printStackTrace();23                 }finally{24                     lock.unlock();//释放锁25                 }26                 System.out.println(Thread.currentThread().getName()+"==》继续执行");27             }    28         }.start();29         new Thread(){30             @Override31             public void run() {32                 lock.lock();//请求锁33                 try{34                     System.out.println(Thread.currentThread().getName()+"=》进入");35                     Thread.sleep(2000);//休息2秒36                     condition.signal();//随机唤醒等待队列中的一个线程37                     System.out.println(Thread.currentThread().getName()+"休息结束");38                 }catch (InterruptedException e) {39                     e.printStackTrace();40                 }finally{41                     lock.unlock();//释放锁42                 }43             }    44         }.start();45     }46 }
复制代码

 

执行结果:

Thread-0==》进入等待Thread-1=》进入Thread-1休息结束Thread-0==》继续执行

流程:在调用await()方法前线程必须获得重入锁(第17行代码),调用await()方法后线程会释放当前占用的锁。同理在调用signal()方法时当前线程也必须获得相应重入锁(代码32行),调用signal()方法后系统会从condition.await()等待队列中唤醒一个线程。当线程被唤醒后,它就会尝试重新获得与之绑定的重入锁,一旦获取成功将继续执行。所以调用signal()方法后一定要释放当前占用的锁(代码41行),这样被唤醒的线程才能有获得锁的机会,才能继续执行。

三、JDK中对Condition 的使用

  我们来看看java.util.concurrent.ArrayBlockingQueue;

  基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

  看看他的put方法:

复制代码
  public void put(E e) throws InterruptedException {        checkNotNull(e);//对传入元素的null判断        final ReentrantLock lock = this.lock;        lock.lockInterruptibly();//对put()方法做同步        try {            while (count == items.length)//如果队列已满                notFull.await();//让当前添加元素的线程进入等待状态            insert(e);// 如果有其他线程调用signal() 通知该线程 ,则进行添加行为        } finally {            lock.unlock();//释放锁        }    }        private E extract() {        final Object[] items = this.items;        E x = this.<E>cast(items[takeIndex]);        items[takeIndex] = null;        takeIndex = inc(takeIndex);        --count;        notFull.signal();//唤醒一个在Condition等待队列中的线程        return x;    }

阅读全文
2 0
原创粉丝点击