参考:
ArrayBlockingQueue与LinkedBlockingQueue对比
Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
一、Object + synchronized
wait
,notify
,notifyAll
是定义在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