Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

参考:

ArrayBlockingQueue与LinkedBlockingQueue对比

Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

java中Condition类的详细介绍(详解)

一、Object + synchronized

waitnotifynotifyAll 是定义在Object类的实例方法,用于控制线程状态

三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException ,意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。

  • wait

表示持有对象锁的线程A准备释放对象锁权限,释放cpu资源并进入等待。

  • notify

表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒某个竞争该对象锁的线程X。线程A synchronized 代码作用域结束后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的notify ,notifyAll被调用)。

  • notifyAll

表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒所有竞争该对象锁的线程,线程A synchronized 代码作用域结束后,jvm通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程X synchronized 代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。

wait有三个重载方法,同时必须捕获非运行时异常InterruptedException。

  • wait()

进入等待,需要notify ,notifyAll才能唤醒

  • wait(long timeout)

进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒

  • wait(timeout, nanos)

进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒。相对wait(long timeout) 更加精确时间。

demo

package day3;

/**
 *  Object + synchronized
 */
public class TestObject {

    public static final Object object = new Object();

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        thread1.start();

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread2.start();
    }

    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    System.out.println("e = " + e.getMessage());
                }
                System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
            }
        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println("线程" + Thread.currentThread().getName() + "调用了object.notify()");
            }
            System.out.println("线程" + Thread.currentThread().getName() + "释放了锁");
        }
    }
}
//线程Thread-1调用了object.notify()
//线程Thread-1释放了锁
//线程Thread-0获取到了锁

二、Lock(ReentrantLock) + Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
  • Condition 中的await()对应Object的wait()
  • Condition 中的signal()对应Object的notify()
  • Condition 中的signalAll()对应Object的notifyAll()

ReentrantLock内部维护了一个双向链表,链表上的每个节点都会保存一个线程,锁在双向链表的头部自选,取出线程执行。而Condition内部同样维持着一个双向链表,但是其向链表中添加元素(await)和从链表中移除(signal)元素没有像ReentrantLock那样,保证线程安全,所以在调用Condition的await()和signal()方法时,需要在lock.lock()和lock.unlock()之间以保证线程的安全。

在调用Condition的signal时,它从自己的双向链表中取出一个节点放到了ReentrantLock的双向链表中,所以在具体的运行过程中不管ReentrantLock new 了几个Condition其实内部公用的一把锁。

demo1

package day3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * Lock(ReentrantLock) + Condition
 * Condition的执行方式,是
 * 当在线程Consumer中调用 [await] 方法后,线程Consumer将释放锁,并且将自己沉睡,等待唤醒,
 * 线程Producer获取到锁后,开始做事,完毕后,
 * 调用Condition的 [signalAll] 方法,唤醒线程Consumer,线程Consumer恢复执行。
 */
public class TestCondition {

    final Lock lock = new ReentrantLock();
    final Condition condition = lock.newCondition();

    public static void main(String[] args) {
        TestCondition test = new TestCondition();

        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();

        consumer.start();
        producer.start();
    }

    class Consumer extends Thread {

        @Override
        public void run() {
            consume();
        }

        private void consume() {
            try {
                lock.lock();
                System.out.println("Consumer 在等一个新信号" + currentThread().getName());
                condition.await();
            } catch (InterruptedException e) {
                System.out.println("Consumer e = " + e.getMessage());
            } finally {
                System.out.println("Consumer 拿到一个信号" + currentThread().getName());
                lock.unlock();
            }
        }
    }

    class Producer extends Thread {

        @Override
        public void run() {
            produce();
        }

        private void produce() {
            try {
                lock.lock();
                System.out.println("Producer 拿到锁" + currentThread().getName());
                condition.signalAll();
                //or
                // condition.signal();
                System.out.println("Producer 发出了一个信号:" + currentThread().getName());
            } catch (Exception e) {
                System.out.println("Producer e = " + e.getMessage());
            } finally {
                lock.unlock();
            }
        }
    }
}
//Consumer 在等一个新信号Thread-1
//Producer 拿到锁Thread-0
//Producer 发出了一个信号:Thread-0
//Consumer 拿到一个信号Thread-1

demo2

package day3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 用synchronized与wait()和notify()/notifyAll()方法结合可以实现等待/通知模式。
 * 但是,在使用notify()/notifyAll()方法进行通知时,被通知的线程却是由JVM随机选择的。
 *
 * 为了摆脱这种窘境,
 * Java在1.5引入了ReentrantLock和Condition类结合使用来达到有选择性的进行线程通知,在调度线程上更加灵活。
 *
 * Object + synchronized / Lock(ReentrantLock) + Condition
 *
 * Object类中的wait()方法相当于Condition类中await()方法。
 * Object类中的wait(long time)方法相当于Condition类中await(long time,TimeUnit unit)方法。
 * Object类中的notify()方法相当于Condition类中signal()方法。
 * Object类中的notifyAll()方法相当于Condition类中signalAll()方法。
 *
 * wait() 进入等待,需要notify ,notifyAll才能唤醒
 * wait(long timeout) 进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒
 * wait(timeout, nanos) 进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒。
 *                  相对wait(long timeout) 更加精确时间。
 *
 * 使用ReentrantLock对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式,比如部分唤醒
 */
public class TestCondition2 {

    final Lock lock = new ReentrantLock();
    final Condition conditionA = lock.newCondition();
    final Condition conditionB = lock.newCondition();

    public static void main(String[] args) {
        TestCondition2 test = new TestCondition2();

        A a = test.new A();
        B b = test.new B();

        a.start();
        b.start();

        try {
            Thread.sleep(3000);
            a.signalA(); //仅唤醒A
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class A extends Thread {

        @Override
        public void run() {
            awaitA();
        }

        private void awaitA() {
            try {
                lock.lock();
                System.out.println("A 在等一个新信号" + currentThread().getName());
                conditionA.await();
            } catch (InterruptedException e) {
                System.out.println("A e = " + e.getMessage());
            } finally {
                System.out.println("A 拿到一个信号" + currentThread().getName());
                lock.unlock();
            }
        }

        public void signalA() {
            try {
                lock.lock();
                System.out.println("A signalA()时间为:" + System.currentTimeMillis());
                conditionA.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }

    class B extends Thread {

        @Override
        public void run() {
            awaitB();
        }

        private void awaitB() {
            try {
                lock.lock();
                System.out.println("B 在等一个新信号" + currentThread().getName());
                conditionB.await();
            } catch (InterruptedException e) {
                System.out.println("B e = " + e.getMessage());
            } finally {
                System.out.println("B 拿到一个信号" + currentThread().getName());
                lock.unlock();
            }
        }

        public void signalB() {
            try {
                lock.lock();
                System.out.println("B signalB()时间为:" + System.currentTimeMillis());
                conditionB.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }
}
//B 在等一个新信号Thread-1
//A 在等一个新信号Thread-0
//A signalA()时间为:1620447561761
//A 拿到一个信号Thread-0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值