多线程并发之显示锁Lock与其通信方式Condition源码解读

【1】显示锁和隐式锁

在Java 5.0 之前,协调共享对象的访问时可以使用的机制只有synchronized 和volatile(保证内存可见性)。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。即,显示锁(同步锁)-Lock。

用户解决多线程安全问题的三种方式:

  • synchronized同步代码块;
  • synchronized同步方法;
  • 同步锁Lock。

其中synchronized被称之为隐式锁,Lock被称之为显示锁。

何为显示锁?
.
即需要显示通过lock()方法上锁,且必须显示通过unlock()方法进行释放锁。
通常为了保证锁的释放,将unlock()方法放在finally中。

在java.util.concurrent.locks包下关于锁与同步辅助类如下:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第1张图片

Lock接口主要实现类如下:
多线程并发之显示锁Lock与其通信方式Condition源码解读_第2张图片

java.util.concurrent.locks包下Lock的实现类主要有ReentrantLock以及ReentrantReadWriteLock中的内部类ReadLock和WriteLock。在使用Lock实现同步锁功能时,通常使用其实现类ReentrantLock。

另外需要注意的是ReentrantReadWriteLock并非是ReentrantLock的子类,而只是ReadWriteLock接口的实现类。如下所示:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第3张图片

【2】多线程卖票问题与Lock实例

先来看一个多线程卖票问题:

public class TestLock {
    public static void main(String[] args){

        Ticket ticket = new Ticket();
        new Thread(ticket,"1号窗口").start();
        new Thread(ticket,"2号窗口").start();
        new Thread(ticket,"3号窗口").start();

    }
}
class Ticket implements  Runnable{

    private  int ticket = 100;

    @Override
    public void run() {

        while (true){
            try {
                //放大多线程问题
                Thread.sleep(200);
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+" 完成售票,余票为:"+--ticket);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

测试结果如下图所示:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第4张图片

这里可能有同学会说,参考从内存可见性看Volatile,使用Volatile,其可以使共享数据在多个线程中保持内存可见性,并能够将数据变动通知其他线程。

那么同样存在问题,正如那篇博文介绍的那样,volatile不具有互斥性,不支持变量原子操作。

如下所示:

public class TestLock {
    public static void main(String[] args){

        Ticket ticket = new Ticket();
        new Thread(ticket,"1号窗口").start();
        new Thread(ticket,"2号窗口").start();
        new Thread(ticket,"3号窗口").start();

    }
}
class Ticket implements  Runnable{
	//使用volatile修饰
    private volatile int ticket = 100;

    @Override
    public void run() {

        while (true){
            try {
                //放大多线程问题
                Thread.sleep(200);
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+" 完成售票,余票为:"+--ticket);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

测试如下图:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第5张图片

不光数据不准备,甚至还有负数!

多线程并发之显示锁Lock与其通信方式Condition源码解读_第6张图片

synchronized当然可以解决这个问题,不过这里不使用synchronized,使用lock。

ReentrantLock 实现了Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。

修改代码如下:

public class TestLock {
    public static void main(String[] args){

        Ticket ticket = new Ticket();
        new Thread(ticket,"1号窗口").start();
        new Thread(ticket,"2号窗口").start();
        new Thread(ticket,"3号窗口").start();

    }
}
class Ticket implements  Runnable{

    private volatile int ticket = 100;
	// ReentrantLock 可重入锁  Lock接口的实现类之一
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true){
            try {
                lock.lock();
                //放大多线程问题
                Thread.sleep(200);
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+" 完成售票,余票为:"+--ticket);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //保证锁的释放
                lock.unlock();
            }
        }

    }
}

再次测试,结果正常!

多线程并发之显示锁Lock与其通信方式Condition源码解读_第7张图片

【3】经典实例生产者与消费者回顾

生产者与消费者实例主要考虑的是进程通信和多线程问题。

没有加进程通信实例如下:

public class TestProductConsumer {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();

    }
}
//店员
class Clerk{

    private int product=0;
    //进货 方法使用synchronized 修饰
    public synchronized void product(){
        if (product>=10){
            System.out.println("产品已满");
        }else{
            System.out.println(Thread.currentThread().getName()+":"+(++product));
        }
    }
    //卖货 方法使用synchronized 修饰
    public synchronized void sale(){
        if(product<=0){
            System.out.println("缺货!");
        }else{
            System.out.println(Thread.currentThread().getName()+":"+(--product));
        }
    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.product();
        }
    }
}
class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

测试结果如下:

生产者A:1
生产者A:2
生产者A:3
生产者A:4
生产者A:5
生产者A:6
生产者A:7
生产者A:8
生产者A:9
生产者A:10
产品已满
产品已满
产品已满
产品已满
产品已满
产品已满
产品已满
产品已满
产品已满
产品已满
消费者B:9
消费者B:8
消费者B:7
消费者B:6
消费者B:5
消费者B:4
消费者B:3
消费者B:2
消费者B:1
消费者B:0
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!

由于循环次数和产品数量不一致,会出现过剩生产和超额消费问题,此时就需要进程间通信。

添加进程间通信实例如下:

public class TestProductConsumer {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();

    }
}
//店员
class Clerk{

    private int product=0;
    //进货
    public synchronized void product(){
        if (product>=10){
            System.out.println("产品已满");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
        }
    }
    //卖货
    public synchronized void sale(){
        if(product<=0){
            System.out.println("缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
        }
    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.product();
        }
    }
}
class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

测试结果如下:

生产者A :当前有 1
生产者A :当前有 2
生产者A :当前有 3
生产者A :当前有 4
生产者A :当前有 5
生产者A :当前有 6
生产者A :当前有 7
生产者A :当前有 8
生产者A :当前有 9
生产者A :当前有 10
产品已满
消费者B :余剩有 9
消费者B :余剩有 8
消费者B :余剩有 7
消费者B :余剩有 6
消费者B :余剩有 5
消费者B :余剩有 4
消费者B :余剩有 3
消费者B :余剩有 2
消费者B :余剩有 1
消费者B :余剩有 0
缺货!
生产者A :当前有 1
生产者A :当前有 2
生产者A :当前有 3
生产者A :当前有 4
生产者A :当前有 5
生产者A :当前有 6
生产者A :当前有 7
生产者A :当前有 8
生产者A :当前有 9
消费者B :余剩有 8
消费者B :余剩有 7
消费者B :余剩有 6
消费者B :余剩有 5
消费者B :余剩有 4
消费者B :余剩有 3
消费者B :余剩有 2
消费者B :余剩有 1
消费者B :余剩有 0

【4】生产者消费者之虚假唤醒

首先修改代码如下:

public class TestProductConsumer {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();

    }
}
//店员
class Clerk{

    private int product=0;
    //进货
    public synchronized void product(){
//        if (product>=10){
		//修改为1 一个产品即满
        if (product>=1){
            System.out.println("产品已满");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
        }
    }
    //卖货
    public synchronized void sale(){
        if(product<=0){
            System.out.println("缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
        }
    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            try {
	            //睡眠0.2s
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
             System.out.println(" this product i : "+i);
        }
    }
}
class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
            System.out.println(" this Consumer i : "+i);
        }
    }
}

测试结果如下:
多线程并发之显示锁Lock与其通信方式Condition源码解读_第8张图片

缺货!
生产者A :当前有 1
 this product i : 0
 this Consumer i : 0
消费者B :余剩有 0
 this Consumer i : 1
缺货!
生产者A :当前有 1
 this product i : 1
 this Consumer i : 2
消费者B :余剩有 0
 this Consumer i : 3
缺货!
生产者A :当前有 1
 this product i : 2
 this Consumer i : 4
消费者B :余剩有 0
 this Consumer i : 5
缺货!
生产者A :当前有 1
 this product i : 3
 this Consumer i : 6
消费者B :余剩有 0
 this Consumer i : 7
缺货!
生产者A :当前有 1
 this Consumer i : 8
 this product i : 4
消费者B :余剩有 0
 this Consumer i : 9
缺货!
生产者A :当前有 1
 this product i : 5
 this Consumer i : 10
消费者B :余剩有 0
 this Consumer i : 11
缺货!
生产者A :当前有 1
 this product i : 6
 this Consumer i : 12
消费者B :余剩有 0
 this Consumer i : 13
缺货!
生产者A :当前有 1
 this Consumer i : 14
 this product i : 7
消费者B :余剩有 0
 this Consumer i : 15
缺货!
生产者A :当前有 1
 this product i : 8
 this Consumer i : 16
消费者B :余剩有 0
 this Consumer i : 17
缺货!
生产者A :当前有 1
 this product i : 9
 this Consumer i : 18
消费者B :余剩有 0
 this Consumer i : 19
生产者A :当前有 1
 this product i : 10
产品已满

很可怕,为什么进程一直卡着了那里不往下继续了(生产者A执行第十次循环时被阻塞着)?由于if-else关系消费者执行了20次循环但是只消耗了10个产品。生产者只有最后一次才会打印产品已满,进行阻塞。

解释如下:

由于生产者run方法里面sleep了0.2S,进程执行速度慢于消费者。消费者首先开始执行,发现product==0;则阻塞让生产者A开始生产。

生产者A生产后++product唤醒消费者B ,消费者执行wait后的空代码;再次循环开始即使生产者A开始执行下一次循环了,但是由于sleep了0.2S,被消费者B抢先拿到了锁!消费者执行--product后,唤醒所有(这个过程中消费者循环次数明显多于生产者)。。。

在某一次消费者B使用完消费次数后(消费者使用完消费次数后,生产者还没有!),这次product==0;生产者A开始执行++product,然后notifyAll。但是这是只剩下了生产者A,生产者再次获取锁,然后判断提示“产品已满”,然后wait。但是这是已经没有线程来唤醒生产者A!!!

如何解决?尝试将else去掉,修改代码如下:

//店员
class Clerk{

    private int product=0;
    //进货
    public synchronized void product(){
        if (product>=1){
            System.out.println(Thread.currentThread().getName()+"产品已满");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        else{
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
//        }
    }
    //卖货
    public synchronized void sale(){
        if(product<=0){
            System.out.println(Thread.currentThread().getName()+"缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        else{
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
//        }
    }
}

这样每次wait后被唤醒会接着wait的代码继续往下执行,就不会出现上述问题(消费者多消耗循环次数),测试结果如下:

消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0
消费者B缺货!
生产者A :当前有 1
消费者B :余剩有 0

仔细对比一下,此时生产者A和消费者B都循环了20次!!而上面那个例子当消费者消耗完循环次数时,生产者还没有结束(那你生产一个后,再等待,肯定阻塞啊)!!!

看起来很美好,但是此时只有一个生产者一个消费者,如果有多个生产者多个消费者呢,修改代码如下:

public class TestProductConsumer {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);
		//多添加两个线程
        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();
        new Thread(product,"生产者C").start();
        new Thread(consumer,"消费者D").start();

    }
}
//店员
class Clerk{

    private int product=0;
    //进货
    public synchronized void product(){
//        if (product>=10){
        if (product>=1){
            System.out.println(Thread.currentThread().getName()+"产品已满");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //去掉else
//        else{
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
//        }
    }
    //卖货
    public synchronized void sale(){
        if(product<=0){
            System.out.println(Thread.currentThread().getName()+"缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        else{
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
//        }
    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
        }
    }
}
class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

测试结果如下:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第9张图片

这里稍微解释如下:

假设两个消费者同时进行消费,一个拿到锁之后发现product<=0;则进行等待,释放锁。另外一个消费者抢到了锁,发现product<=0;则进行等待,并且也释放锁(因为生产者在拿到锁之前sleep了0.2S,故同等情况下,消费者更容易抢先占有锁)。

然后如果这是它们被唤醒会怎样?会继续从wait的代码往下执行,并不会对product再进行判断!!!这时出现负值也不奇怪了-----这种现象就是虚假唤醒!

查看Object中wait方法的源码:

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 * 

* The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. *

//注意这里!!!在当前这个争论版本中,中断和虚假唤醒是可能的, //因此你应该在一个循环中使用这个方法!! * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: *

 *     synchronized (obj) {
 *         while (<condition does not hold>)
 *             obj.wait();
 *         ... // Perform action appropriate to condition
 *     }
 * 
* This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The interrupted * status of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { wait(0); }

即,最好在一个while循环中使用wait,让其每次被唤醒后重新进行判断,修改方法如下:

public class TestProductConsumer {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();
        new Thread(product,"生产者C").start();
        new Thread(consumer,"消费者D").start();

    }
}
//店员
class Clerk{

    private int product=0;
    //进货
    public synchronized void product(){
        while(product>=1){
            System.out.println(Thread.currentThread().getName()+"产品已满");
            try {
            //唤醒后会继续进行while循环判断
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        else{
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
//        }
    }
    //卖货
    public synchronized void sale(){
        while(product<=0){
            System.out.println(Thread.currentThread().getName()+"缺货!");
            try {
	            //唤醒后会继续进行while循环判断
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        else{
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
//        }
    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
        }
    }
}
class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

测试再次测试结果如下:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第10张图片

【5】生产者消费者之Lock

上面实例使用的是synchronized,那么同样可以使用Lock实现。

代码如下:

public class TestProductConsumer2 {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();
        new Thread(product,"生产者C").start();
        new Thread(consumer,"消费者D").start();

    }
}

//店员
class Clerk{

    private int product=0;

    private Lock lock = new ReentrantLock();
    //进货
    public  void product(){
        lock.lock();
        try {
            while(product>=1){
                System.out.println(Thread.currentThread().getName()+"产品已满");
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            this.notifyAll();
        }finally {
            lock.unlock();
        }

    }
    //卖货
    public void sale(){
        lock.lock();
        try{
            while(product<=0){
                System.out.println(Thread.currentThread().getName()+"缺货!");
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            this.notifyAll();
        }finally{
            lock.unlock();
        }

    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
        }
    }
}

class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

但是这里需要注意的是,此时用的是lock这把锁,但是进程通信使用的是this.wait();this.notifyAll();非lock的通信方式,此时运行程序是会抛IllegalMonitorStateException异常,如下图所示:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第11张图片
当然,lock也有其进程通信方式–Condition!

【6】进程通信之Condition

Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器类似,但提供了更强大的功能。

需要特别指出的是,单个Lock 可能与多个Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的Object 版本中的不同。

在Condition 对象中,与wait、notify 和notifyAll 方法对应的分别是await、signal 和signalAll。

Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。

 private Lock lock = new ReentrantLock();
 //获取condition
 private Condition condition = lock.newCondition();

修改代码如下:

public class TestProductConsumer2 {
    public static void main(String[] args){

        Clerk clerk = new Clerk();
        Product product = new Product(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(product,"生产者A").start();
        new Thread(consumer,"消费者B").start();
        new Thread(product,"生产者C").start();
        new Thread(consumer,"消费者D").start();

    }
}

//店员
class Clerk{

    private int product=0;

    private Lock lock = new ReentrantLock();
    //获取condition
    private Condition condition = lock.newCondition();

    //进货
    public  void product(){
        lock.lock();
        try {
            while(product>=1){
                System.out.println(Thread.currentThread().getName()+"产品已满");
                try {
	                //使用condition的await
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" :当前有 "+(++product));
            //使用condition的signalAll
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
    //卖货
    public void sale(){
        lock.lock();
        try{
            while(product<=0){
                System.out.println(Thread.currentThread().getName()+"缺货!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" :余剩有 "+(--product));
            condition.signalAll();
        }finally{
            lock.unlock();
        }

    }
}

class Product implements  Runnable{

    private Clerk clerk;

    public Product(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.product();
        }
    }
}

class Consumer implements  Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i = 0;i<20;i++){
            clerk.sale();
        }
    }
}

再次运行程序,结果正常!

可能有人会说,Object是所有类的父类,那么Condition肯定也有wait、notifyAll等方式,为什么不使用condition.wait; condition.notifyAll

这里需要注意的是,此时的锁(同步对象监视器)为lock,不再是this,不再是其他。故而,你需要在lock上面使用进程通信—其方式为condition!如果使用condition.wait; condition.notifyAll程序会抛出IllegalMonitorStateException异常。

如下图所示:

多线程并发之显示锁Lock与其通信方式Condition源码解读_第12张图片

【7】Condition接口源码解读

上面的实例可能稍微了解了一下Condition究竟是什么,但是还不足够。下面从接口源码来看一下它究竟是个什么东西。

Condition接口源码如下:

 * {@code Condition} factors out the {@code Object} monitor
 * methods ({@link Object#wait() wait}, {@link Object#notify notify}
 * and {@link Object#notifyAll notifyAll}) into distinct objects to
 * give the effect of having multiple wait-sets per object, by
 * combining them with the use of arbitrary {@link Lock} implementations.
 * 
 * Condition将 Object监视器方法wait()、notify 和notifyAll 分解
 * 为不同的对象,通过将它们与任意的Lock实现结合使用,来赋予每个对象具有多个等待集的效果。
 * 
 * Where a {@code Lock} replaces the use of {@code synchronized} methods
 * and statements, a {@code Condition} replaces the use of the Object
 * monitor methods.
 *在使用lock替代synchronized的地方,就会使用Condition的通信方法替换Object监视器的方法。
 
 * 

Conditions (also known as condition queues or * condition variables) provide a means for one thread to * suspend execution (to "wait") until notified by another * thread that some state condition may now be true. * * Condition(或者说常见的condition 队列和condition 变量)为线程提供了一种方式暂停执行当前线程, * 直到某个条件状态为真时,将会被另外一个线程唤醒。 * * Because access to this shared state information occurs in different threads, it * must be protected, so a lock of some form is associated with the condition. * 因为对这个共享状态信息的访问发生在不同的线程中,所以必须对其进行保护, * 所以某种形式的锁与条件相关联(也就是说,Condition是绑定在Lock上面)。 * * The key property that waiting for a condition provides * is that it atomically releases the associated lock and * suspends the current thread, just like {@code Object.wait}. *一个condition 提供的wait的关键属性是它原子性地释放相关联的锁并挂起当前线程, 就像{@code Object.wait}效果一样。 *

A {@code Condition} instance is intrinsically bound to a lock. 一个Condition实例本质上绑定在一个Lock实例对象上。 * To obtain a {@code Condition} instance for a particular {@link Lock} * instance use its {@link Lock#newCondition newCondition()} method. *要为特定的Lock实例获取Condition实例,使用Lock.newConditon()方法。 *

As an example, suppose we have a bounded buffer which supports * {@code put} and {@code take} methods. * 举一个例子,假设我们有一个大小有限的缓冲池支持put和take方法。 * If a {@code take} is attempted on an empty buffer, then the thread will block * until an item becomes available; if a {@code put} is attempted on a * full buffer, then the thread will block until a space becomes available. * 如果企图从空的缓冲池里面获取元素,这个线程将会阻塞直到有一个元素可用; * 如果企图往满的缓冲池里面放入元素,这个线程将会阻塞直到缓冲池有一个空间。 * * We would like to keep waiting {@code put} threads and {@code take} * threads in separate wait-sets so that we can use the optimization of * only notifying a single thread at a time when items or spaces become * available in the buffer. * 我们希望将等待{@code put}线程和{@code.}线程保持在单独的等待集中, * 这样在缓冲区中元素可用或存在空间时每次只需要通知单个线程。 * * This can be achieved using two {@link Condition} instances. * 这种方式就可以使用两个Condition实例。 * class BoundedBuffer { * final Lock lock = new ReentrantLock(); * final Condition notFull = lock.newCondition(); * final Condition notEmpty = lock.newCondition(); * final Object[] items = new Object[100]; * int putptr, takeptr, count; * public void put(Object x) throws InterruptedException { * lock.lock(); * try { * while (count == items.length) * notFull.await(); * items[putptr] = x; * if (++putptr == items.length) putptr = 0; * ++count; * notEmpty.signal(); * } finally { * lock.unlock(); * } * } * * public Object take() throws InterruptedException { * lock.lock(); * try { * while (count == 0) * notEmpty.await() * Object x = items[takeptr]; * if (++takeptr == items.length) takeptr = 0; * --count; * notFull.signal(); * return x; * } finally { * lock.unlock(); * } * } * } * (The {@link java.util.concurrent.ArrayBlockingQueue} class provides * this functionality, so there is no reason to implement this sample usage class.) ArrayBlockingQueue类已经实现了这个功能,所以没有必要再去实现上面这个使用例子。 *

A {@code Condition} implementation can provide behavior and semantics * that is different from that of the {@code Object} monitor methods, such as * guaranteed ordering for notifications, or not requiring a lock to be held * when performing notifications. *一个 Condition的实现可以提供与Object监视器方法不同的行为和语义, 例如保证唤醒的顺序,或者在执行唤醒时不需要持有锁 * If an implementation provides such specialized semantics then the * implementation must document those semantics. *如果有某种实现提供这样的专门语义,那么实现必须记录那些语义。 *

Note that {@code Condition} instances are just normal objects and can * themselves be used as the target in a {@code synchronized} statement, * and can have their own monitor {@link Object#wait wait} and * {@link Object#notify notification} methods invoked. * 需要注意的是Condition实例只是普通对象,它们可以被用来作为synchronized语句的目标; * 也可以由自己的监视器方法调用,如Object.wait和Object.notify。 * * Acquiring the monitor lock of a {@code Condition} instance, or using its * monitor methods, has no specified relationship with acquiring the * {@link Lock} associated with that {@code Condition} or the use of its * {@linkplain #await waiting} and {@linkplain #signal signalling} methods. * 获取 Condition实例的监视器锁或使用其监视器方法, * 与获取 和Condition相关联的Lock或使用await和signal 方法没有特定的关系。 * * It is recommended that to avoid confusion you never use {@code Condition} * instances in this way, except perhaps within their own implementation. *为了避免混淆,建议不要以这种方式使用{@code Condition}实例, 除非是在它们自己的实现类中。 *

Implementation Considerations

* *

When waiting upon a {@code Condition}, a "spurious * wakeup" is permitted to occur, in * general, as a concession to the underlying platform semantics. * 当等待 Condition时,通常允许发生伪唤醒作为对底层平台语义的让步 * * This has little practical impact on most application programs as a * {@code Condition} should always be waited upon in a loop, testing * the state predicate that is being waited for. * 这对大多数应用程序几乎没有实际影响,因为Condition应该始终在循环中等待, * 测试正在等待的predicate 状态。 * * An implementation is free to remove the possibility of spurious wakeups but it is * recommended that applications programmers always assume that they can * occur and so always wait in a loop. *实现可以消除虚假唤醒的可能性,但是建议应用程序编程人员总是假定它们可能发生, 因此总是在循环中等待。 *

The three forms of condition waiting * (interruptible, non-interruptible, and timed) may differ in their ease of * implementation on some platforms and in their performance characteristics. * Condition waiting的三种形式(可中断的、不可中断的和定时的)。 * 在某些平台上的实现容易程度和性能特征方面可能不同。 * * In particular, it may be difficult to provide these features and maintain * specific semantics such as ordering guarantees. * 特别是,可能难以提供这些特征并保持特定语义,例如排序保证。 * * Further, the ability to interrupt the actual suspension of the thread may * not always be feasible to implement on all platforms. *此外,中断线程的实际暂停的能力在所有平台上可能并不总是可行的 *

Consequently, an implementation is not required to define exactly the * same guarantees or semantics for all three forms of waiting, nor is it * required to support interruption of the actual suspension of the thread. *因此,对于所有三种形式的等待,不需要实现定义完全相同的保证或语义, 也不需要支持中断线程的实际挂起。 *

An implementation is required to * clearly document the semantics and guarantees provided by each of the * waiting methods, and when an implementation does support interruption of * thread suspension then it must obey the interruption semantics as defined * in this interface. *一个实现需要清楚地记录每个等待方法提供的语义和保证,并且当实现确实支持线程挂起的中断时, 它必须遵守该接口中定义的中断语义。 *

As interruption generally implies cancellation, and checks for * interruption are often infrequent, an implementation can favor responding * to an interrupt over normal method return. * 由于中断通常意味着取消,而且对中断的检查通常不常见, * 所以实现可能更倾向于响应中断而不是正常的方法返回。 * This is true even if it can be * shown that the interrupt occurred after another action that may have * unblocked the thread. An implementation should document this behavior. * 这是真的,即使它可以表明中断发生在可能解除线程的另一个动作之后。 * 实现应该记录此行为。 * * @since 1.5 */ public interface Condition { /** * Causes the current thread to wait until it is signalled or * {@linkplain Thread#interrupt interrupted}. * 使当前线程等待直到其被唤醒或者被中断。 * *

The lock associated with this {@code Condition} is atomically * released and the current thread becomes disabled for thread scheduling * purposes and lies dormant until one of four things happens: * 与Condition相关联的锁将会被原子性地释放并且由于线程调度当前线程将会不可用 * 并处于休眠状态直到以下四种情况发生: *

    *
  • Some other thread invokes the {@link #signal} method for this * {@code Condition} and the current thread happens to be chosen as the * thread to be awakened; or * 其他一些线程调用这个Condition的signal方法,并且当前线程正好被选择为要唤醒的线程; *
  • Some other thread invokes the {@link #signalAll} method for this * {@code Condition}; or * 其他一些线程调用这个Condition的signalAll方法 *
  • Some other thread {@linkplain Thread#interrupt interrupts} the * current thread, and interruption of thread suspension is supported; or *
  • A "spurious wakeup" occurs. *
* 其他一些线程中断当前线程,并且中断线程挂起是被支持的; * 或者发生了一个“虚假唤醒”。 * *

In all cases, before this method can return the current thread must * re-acquire the lock associated with this condition. When the * thread returns it is guaranteed to hold this lock. *在所有情况下,在该方法返回之前,当前线程必须重新获取与此条件相关联的锁。 当线程返回时,它保证持有这个锁。 *

If the current thread: *

    *
  • has its interrupted status set on entry to this method; or *
  • is {@linkplain Thread#interrupt interrupted} while waiting * and interruption of thread suspension is supported, *
* then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * 如果当前线程:的中断状态被设置在该方法的条目上,或者在等待时被中断如果支持中断线程挂起, * 则抛出InterruptedException,并且清除当前线程的中断状态 * It is not specified, in the first * case, whether or not the test for interruption occurs before the lock * is released. * 在第一种情况下,中断测试是否发生在锁之前没有被指定。 * 接口实现类需要考虑的事项 *

Implementation Considerations * *

The current thread is assumed to hold the lock associated with this * {@code Condition} when this method is called. * 当这个方法被调用时,当前线程被认为拥有与Condition关联的锁。 * It is up to the implementation to determine if this is * the case and if not, how to respond. Typically, an exception will be * thrown (such as {@link IllegalMonitorStateException}) and the * implementation must document that fact. * *

An implementation can favor responding to an interrupt over normal * method return in response to a signal. In that case the implementation * must ensure that the signal is redirected to another waiting thread, if * there is one. * * @throws InterruptedException if the current thread is interrupted * (and interruption of thread suspension is supported) */ void await() throws InterruptedException; /** * Causes the current thread to wait until it is signalled. * *

The lock associated with this condition is atomically * released and the current thread becomes disabled for thread scheduling * purposes and lies dormant until one of three things happens: *

    *
  • Some other thread invokes the {@link #signal} method for this * {@code Condition} and the current thread happens to be chosen as the * thread to be awakened; or *
  • Some other thread invokes the {@link #signalAll} method for this * {@code Condition}; or *
  • A "spurious wakeup" occurs. *
* *

In all cases, before this method can return the current thread must * re-acquire the lock associated with this condition. When the * thread returns it is guaranteed to hold this lock. * *

If the current thread's interrupted status is set when it enters * this method, or it is {@linkplain Thread#interrupt interrupted} * while waiting, it will continue to wait until signalled. When it finally * returns from this method its interrupted status will still * be set. * *

Implementation Considerations * *

The current thread is assumed to hold the lock associated with this * {@code Condition} when this method is called. * It is up to the implementation to determine if this is * the case and if not, how to respond. Typically, an exception will be * thrown (such as {@link IllegalMonitorStateException}) and the * implementation must document that fact. */ //与上面void await相比,这种方式等待不支持线程被中断 void awaitUninterruptibly(); /** * Causes the current thread to wait until it is signalled or interrupted, * or the specified waiting time elapses. * *

The lock associated with this condition is atomically * released and the current thread becomes disabled for thread scheduling * purposes and lies dormant until one of five things happens: *

    *
  • Some other thread invokes the {@link #signal} method for this * {@code Condition} and the current thread happens to be chosen as the * thread to be awakened; or *
  • Some other thread invokes the {@link #signalAll} method for this * {@code Condition}; or *
  • Some other thread {@linkplain Thread#interrupt interrupts} the * current thread, and interruption of thread suspension is supported; or *
  • The specified waiting time elapses; or *
  • A "spurious wakeup" occurs. *
* *

In all cases, before this method can return the current thread must * re-acquire the lock associated with this condition. When the * thread returns it is guaranteed to hold this lock. * *

If the current thread: *

    *
  • has its interrupted status set on entry to this method; or *
  • is {@linkplain Thread#interrupt interrupted} while waiting * and interruption of thread suspension is supported, *
* then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. It is not specified, in the first * case, whether or not the test for interruption occurs before the lock * is released. * *

The method returns an estimate of the number of nanoseconds * remaining to wait given the supplied {@code nanosTimeout} * value upon return, or a value less than or equal to zero if it * timed out. This value can be used to determine whether and how * long to re-wait in cases where the wait returns but an awaited * condition still does not hold. Typical uses of this method take * the following form: * *

 {@code
     * boolean aMethod(long timeout, TimeUnit unit) {
     *   long nanos = unit.toNanos(timeout);
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (nanos <= 0L)
     *         return false;
     *       nanos = theCondition.awaitNanos(nanos);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}
* *

Design note: This method requires a nanosecond argument so * as to avoid truncation errors in reporting remaining times. * Such precision loss would make it difficult for programmers to * ensure that total waiting times are not systematically shorter * than specified when re-waits occur. * *

Implementation Considerations * *

The current thread is assumed to hold the lock associated with this * {@code Condition} when this method is called. * It is up to the implementation to determine if this is * the case and if not, how to respond. Typically, an exception will be * thrown (such as {@link IllegalMonitorStateException}) and the * implementation must document that fact. * *

An implementation can favor responding to an interrupt over normal * method return in response to a signal, or over indicating the elapse * of the specified waiting time. In either case the implementation * must ensure that the signal is redirected to another waiting thread, if * there is one. * * @param nanosTimeout the maximum time to wait, in nanoseconds * @return an estimate of the {@code nanosTimeout} value minus * the time spent waiting upon return from this method. * A positive value may be used as the argument to a * subsequent call to this method to finish waiting out * the desired time. A value less than or equal to zero * indicates that no time remains. * @throws InterruptedException if the current thread is interrupted * (and interruption of thread suspension is supported) */ //与上面void await相比,这种方式多了等待时间。等待时间过去也会唤醒。 long awaitNanos(long nanosTimeout) throws InterruptedException; /** * Causes the current thread to wait until it is signalled or interrupted, * or the specified waiting time elapses. This method is behaviorally * equivalent to: *

 {@code awaitNanos(unit.toNanos(time)) > 0}
* * @param time the maximum time to wait * @param unit the time unit of the {@code time} argument * @return {@code false} if the waiting time detectably elapsed * before return from the method, else {@code true} * @throws InterruptedException if the current thread is interrupted * (and interruption of thread suspension is supported) */ boolean await(long time, TimeUnit unit) throws InterruptedException; /** * Causes the current thread to wait until it is signalled or interrupted, * or the specified deadline elapses. * *

The lock associated with this condition is atomically * released and the current thread becomes disabled for thread scheduling * purposes and lies dormant until one of five things happens: *

    *
  • Some other thread invokes the {@link #signal} method for this * {@code Condition} and the current thread happens to be chosen as the * thread to be awakened; or *
  • Some other thread invokes the {@link #signalAll} method for this * {@code Condition}; or *
  • Some other thread {@linkplain Thread#interrupt interrupts} the * current thread, and interruption of thread suspension is supported; or *
  • The specified deadline elapses; or *
  • A "spurious wakeup" occurs. *
* *

In all cases, before this method can return the current thread must * re-acquire the lock associated with this condition. When the * thread returns it is guaranteed to hold this lock. * * *

If the current thread: *

    *
  • has its interrupted status set on entry to this method; or *
  • is {@linkplain Thread#interrupt interrupted} while waiting * and interruption of thread suspension is supported, *
* then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. It is not specified, in the first * case, whether or not the test for interruption occurs before the lock * is released. * * *

The return value indicates whether the deadline has elapsed, * which can be used as follows: *

 {@code
     * boolean aMethod(Date deadline) {
     *   boolean stillWaiting = true;
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (!stillWaiting)
     *         return false;
     *       stillWaiting = theCondition.awaitUntil(deadline);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}
* *

Implementation Considerations * *

The current thread is assumed to hold the lock associated with this * {@code Condition} when this method is called. * It is up to the implementation to determine if this is * the case and if not, how to respond. Typically, an exception will be * thrown (such as {@link IllegalMonitorStateException}) and the * implementation must document that fact. * *

An implementation can favor responding to an interrupt over normal * method return in response to a signal, or over indicating the passing * of the specified deadline. In either case the implementation * must ensure that the signal is redirected to another waiting thread, if * there is one. * * @param deadline the absolute time to wait until * @return {@code false} if the deadline has elapsed upon return, else * {@code true} * @throws InterruptedException if the current thread is interrupted * (and interruption of thread suspension is supported) */ // 与void await相比,这种方式多了一个等待截止期限,期限一过,不再等待。 boolean awaitUntil(Date deadline) throws InterruptedException; /** * Wakes up one waiting thread. *唤醒一个等待的线程。 *

If any threads are waiting on this condition then one * is selected for waking up. That thread must then re-acquire the * lock before returning from {@code await}. *如果不止一个线程在等待这个Condition ,将会从中选取一个唤醒。 被选取的线程在从await返回前必须重新获取Lock。 *

Implementation Considerations * *

An implementation may (and typically does) require that the * current thread hold the lock associated with this {@code * Condition} when this method is called. Implementations must * document this precondition and any actions taken if the lock is * not held. Typically, an exception such as {@link * IllegalMonitorStateException} will be thrown. */ void signal(); /** * Wakes up all waiting threads. *唤醒所有等待的线程。 *

If any threads are waiting on this condition then they are * all woken up. Each thread must re-acquire the lock before it can * return from {@code await}. *任何等待该Condition 的线程都将会被唤醒。 每个线程在能够从await返回前必须重新获取Lock。 *

Implementation Considerations * *

An implementation may (and typically does) require that the * current thread hold the lock associated with this {@code * Condition} when this method is called. Implementations must * document this precondition and any actions taken if the lock is * not held. Typically, an exception such as {@link * IllegalMonitorStateException} will be thrown. */ void signalAll(); }

Condition接口在java.util.concurrent.locks包下只有两个实现类,且分别是是AbstractQueueLongSynchroizer和AbstractQueueSynchronizer的内部类:
在这里插入图片描述

总结Condition如下:

  • Condition 实例实质上被绑定到一个锁上,特定Lock 实例获得Condition 实例,请使用其newCondition() 方法;
  • Condition有其特有通信方法,与wait、notify 和notifyAll 方法对应的分别是await、signal 和signalAll;
  • 单个Lock可能与多个Condition实例关联;
  • 同synchronized一样,在进行条件判断通信时,Condition应该始终在循环中等待,避免虚假唤醒;
  • Condition也是一个普通对象,也有自己的(继承自Object)的wait、notify 和notifyAll方法;
  • 建议不要将condition实例作为synchronized的目标(监视器-monitor);
  • Condition wait的三种形式(可中断的、不可中断的和定时的);

你可能感兴趣的:(多线程并发Thread)