java并发编程 在协作对象_Java并发编程六 线程间的协作方式

线程间的协作指的是多个线程在共同完成某项任务时,需要线程之间互相通信,协调共同完成,最经典的例子就是生产者-消费者模型(网上烂大街的例子...)。

所谓生产者消费者模型是指:有个共同的数据结构(比如阻塞队列)作为生产者线程和消费者线程读和写的临界资源,当队列满时,生产者线程阻塞(挂起),生产者线程释放临界资源的占有权(比如对象锁),直到队列有空间时才能重新获得临界资源的占有权向队列里放入商品;当队列为空时,消费者线程阻塞,消费者线程释放临界资源的占有权,直到队列里有商品时才能重新获得临界资源的占有权消费商品。生产者-消费者模型最经典的例子是线程池。

当生产者生产了一个商品,会去通知消费者可以消费了;当消费者消费了一个商品,会通知生产者可以再生产一个,这就是线程间的通信协作来完成共同的一件事情。

之前介绍的Thread类的实例方法join()也是线程间通信的方式,这里介绍线程协作中最常见的两种方式:Object类的wait()、notify()、notifyAll()和依赖于Lock接口的Condition接口。

1、Object类的wait()、notify()、notifyAll()

1. 1 API说明

/*** Wakes up a single thread that is waiting on this object's* monitor. If any threads are waiting on this object, one of them* is chosen to be awakened. The choice is arbitrary and occurs at* the discretion of the implementation. A thread waits on an object's* monitor by calling one of the wait methods*/

public final native void notify();

/*** Wakes up all threads that are waiting on this object's monitor. A* thread waits on an object's monitor by calling one of the* wait methods.*/

public final native void notifyAll();

/*** Causes the current thread to wait until either another thread invokes the* {@link java.lang.Object#notify()} method or the* {@link java.lang.Object#notifyAll()} method for this object, or a* specified amount of time has elapsed.*

* The current thread must own this object's monitor.*/

public final native void wait(long timeout) throws InterruptedException;

(1)wait()、notify()和notifyAll()方法都是Object类的final方法,无法被重写。

(2)wait()方法,调用某个对象的wait()方法能够让当前线程阻塞,前提是当前线程拥有这个对象的锁,此时线程会释放对象锁,待线程被notify()方法或者notifyAll()方法唤醒并重新获得对象锁后,线程会从阻塞的地方向后继续执行程序。wait()方法里还有个timeout的参数,意思是超过了这个时间(秒级),如果没有其他线程获取了对象锁,调用wait(timeout)方法的线程会重新获取对象锁,执行wait(timeout)方法后面的程序。

(3)notify()方法,调用某个对象的notify()方法能够唤醒一个正在等待这个对象锁的线程,前提是调用notify()方法的线程获取了对象锁。注意:调用完notify()方法后,被唤醒的线程不是立刻就获取对象锁执行任务,而是要等待执行notify()方法的线程执行完剩余任务释放了对象锁后,才能有机会获取到对象锁执行wait()后面的程序,这里有机会是说:线程执行完notify并且执行完同步块里的代码后,该线程处于就绪状态,会和notify唤醒后的线程共同竞争,有可能线程执行完一次notify后,又执行了notify,而没让被唤醒的线程获得锁执行。

(4)notifyAll()方法,调用某个对象的notifyAll()方法可以唤醒正在等待这个对象锁的所有线程,前提是调用notifyAll方法的线程获取了对象锁。

1.2 举例

起两个异步线程,一个是下载线程,用来下载图片和附件,另一个是图片展示线程,用来展示图片,执行顺序是:先下载图片,图片下载完毕后展示图片,图片展示完毕后下载附件。

package Notify;

/*** @ClassName NotifyDemo* @Description TODO* @Auther Jerry* @Date 2020/3/11 - 0:45* @Version 1.0*/

/*** wait()&&notify()方法* 这两个方法是在Object中定义的,用于协调线程同步,比join更加灵活*/

public class NotifyDemo {

public static void main(String[] args) {

Object obj=new Object();

// 下载线程,包括下载图片和下载附件 Thread download=new Thread(){

public void run() {

System.out.println("开始下载图片");

for (int i = 0; i < 101; i+=10) {

System.out.println("down"+i+"%");

try {

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("图片下载成功");

synchronized (obj) {

//唤醒展示图片的线程 obj.notify();

// 阻塞当前线程下载附件,待图片展示线程展示图片完毕后再唤醒下载线程下载附件 try {

obj.wait();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

System.out.println("开始下载附件");

for (int i = 0; i < 101; i+=10) {

System.out.println("附件下载"+i+"%");

try {

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("附件下载成功");

}

};

// 图片展示线程 Thread show=new Thread(){

public void run(){

synchronized (obj) {

try {

// 开始图片还未下载完成,阻塞图片展示线程 obj.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("show:开始展示图片");

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("图片展示完毕");

// 图片展示线程展示完图片后,再通知下载线程下载附件 obj.notify();

}

}

};

// 启动两个线程 download.start();

show.start();

}

}

结果如下:

开始下载图片

down0%

down10%

down20%

down30%

down40%

down50%

down60%

down70%

down80%

down90%

down100%

图片下载成功

show:开始展示图片

图片展示完毕

开始下载附件

附件下载0%

附件下载10%

附件下载20%

附件下载30%

附件下载40%

附件下载50%

附件下载60%

附件下载70%

附件下载80%

附件下载90%

附件下载100%

附件下载成功

1.3 面试会问的点

(1)wait()、notify()和notifyAll()方法为什么是Object类的而不是Thread类的?

只有同一个锁上的被等待线程,才可以被同一个锁上执行notify/notifyAll的线程唤醒,之间沟通的桥梁是这个对象锁,因此方法是Object类而不是Thread类的。

(2)wait()、sleep()、join()、yield()的区别?wait()方法是Object类的实例方法,调用wait()方法会让当前线程阻塞,释放锁;

sleep()方法是Thread类的静态方法,会有入参sleep(timeout),单位是毫秒,调用sleep()会让线程进入阻塞状态,但不会释放锁,待阻塞时间超过timeout时,线程会由阻塞状态变为就绪状态,获得CPU时间片后变为运行状态继续执行任务;

yiled方法是Thread类的静态方法,没有入参,调用yield方法会使线程退出CPU时间片由运行状态变为就绪状态,让其他同优先级或者更高优先级的线程获取CPU时间片进入运行状态,yiled方法不会释放锁;

join()方法是Thread类的实例方法,没有入参,线程执行t.join()方法,线程会等待t线程结束再继续执行,调用线程本身也会阻塞。

2、Condition接口

Condition是一个接口,是java1.5引进的,就是用来替代传统的wait()、notify()和notifyAll()来实现线程间协作通信的,相比之下Condition更加灵活高效,一个Lock实例里可以创建多个Condition对象,完成多路通信(不同线程对应不通的Condition)。相比之下,synchronized块搭配wait()、notify()、notifyAll(),相当于仅有1个Condition,所有线程的调度通信都是由这个Condition完成的,不够灵活。

2.1 API说明

package java.util.concurrent.locks;

import java.util.Date;

import java.util.concurrent.TimeUnit;

public interface Condition {

void await() throws InterruptedException;

void signal();

void signalAll();

}

上面仅列举了Condition接口的三个方法。

(1)Condition是个接口,await方法对应Object类的wait方法,signal方法对应Object类的notify方法,singnalAll方法对应Object类的notifyAll方法;

(2)Condition对象的创建依赖Lock对象,具体创建为:

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

(3)Condition对象的await方法 、signal方法和signalAll方法都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。

2.2 举例

初始化两个Condition对象,在main方法里起两个异步线程,分别调用两个Condition对象的await方法阻塞两个异步线程,然后在主线程中先调用ConditionA的signal方法唤醒线程A,过2秒后在主线程中再调用ConditionB的signal方法唤醒线程B,如下:

package Lock;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/*** @ClassName ConditionDemo* @Description TODO* @Auther Jerry* @Date 2020/3/18 - 22:31* @Version 1.0*/

public class ConditionDemo {

Lock lock = new ReentrantLock();

private Condition conditionA = lock.newCondition();

private Condition conditionB = lock.newCondition();

public static void main(String[] args) {

// 初始化实例 ConditionDemo conditionDemo = new ConditionDemo();

// 启动两个线程,分别调用各自的Condition的await方法令其等待 new Thread(conditionDemo::awaitA).start();

new Thread(conditionDemo::awaitB).start();

// 主线程休眠2s后,先唤醒conditionA对应的线程 try {

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

conditionDemo.signalA();

// 唤醒完conditionA对应的线程后,主线程再休眠2s,再唤醒conditionB对应的线程 try {

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

// conditionDemo.signalB();

}

private void signalA()

{

lock.lock();

try {

System.out.println("唤醒ConditionA对应的线程");

this.conditionA.signal();

}

finally {

lock.unlock();

}

}

private void signalB()

{

lock.lock();

try {

System.out.println("唤醒ConditionB对应的线程");

this.conditionB.signal();

}

finally {

lock.unlock();

}

}

private void awaitA()

{

lock.lock();

try {

System.out.println("ConditionA对应的线程等待...");

try {

conditionA.await();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

finally {

lock.unlock();

}

}

private void awaitB()

{

lock.lock();

try {

System.out.println("ConditionB对应的线程等待...");

try {

conditionB.await();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

finally {

lock.unlock();

}

}

}

运行结果如下 :

ConditionA对应的线程等待...

ConditionB对应的线程等待...

唤醒ConditionA对应的线程

唤醒ConditionB对应的线程

从结果可以看出,主线程可以通过ConditionA和ConditionB分别控制线程A和线程B的休眠和唤醒,这也是Condition相比Object类的wait等方法的一个突出优势:多路通信协调线程。

3、上述两种方式实现生产者-消费者模型

(1)Object类的wait、notify、notifyAll

package Notify;

import java.awt.*;

import java.util.PriorityQueue;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/*** @ClassName DemoObject* @Description TODO* @Auther Jerry* @Date 2020/3/18 - 23:34* @Version 1.0*/

public class DemoObject {

// producer线程和consumer线程共同访问的临界资源 private int queueSize = 10;

private PriorityQueue queue = new PriorityQueue(queueSize);

public static void main(String[] args) {

DemoObject demoObject = new DemoObject();

Producer producer = demoObject.new Producer();

Consumer consumer = demoObject.new Consumer();

producer.start();

consumer.start();

}

class Producer extends Thread{

@Override

public void run() {

produce();

}

private void produce()

{

while (true)

{

synchronized (queue)

{

while (queue.size() == queueSize)

{

try {

System.out.println("队列已满,生产者线程需阻塞");

queue.wait();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

System.out.println("队列未满,生产者线程可以生产一个商品");

// 模仿生产一个商品需要耗时1秒 try{

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

queue.offer(1);

System.out.println("生产者线程生产了一个商品");

// 通知消费者线程可以消费商品了 queue.notifyAll();

}

// 线程让步,使下一次操作可能是生产者生产商品,也可能是消费者消费商品 Thread.yield();

}

}

}

class Consumer extends Thread{

@Override

public void run() {

consume();

}

private void consume()

{

while (true)

{

synchronized (queue)

{

while (queue.isEmpty())

{

try {

System.out.println("队列为空,消费者线程需阻塞");

queue.wait();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

System.out.println("队列非空,消费者线程可以消费一个商品");

// 模仿消费一个商品需要耗时1秒 try{

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

queue.poll();

System.out.println("消费者线程消费了一个商品");

// 通知生产者线程可以生产商品了 queue.notifyAll();

// 线程让步,使下一次操作可能是生产者生产商品,也可能是消费者消费商品 Thread.yield();

}

}

}

}

}

结果不是唯一的,每次运行都可能得到不同的结果,取任意一次结果展示:

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列为空,消费者线程需阻塞

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列为空,消费者线程需阻塞

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列为空,消费者线程需阻塞

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

...

(2)Condition

package Notify;

import ConcurrentOne.ClassTest;

import java.util.PriorityQueue;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/*** @ClassName DemoCondition* @Description TODO* @Auther Jerry* @Date 2020/3/19 - 0:29* @Version 1.0*/

public class DemoCondition {

// producer线程和consumer线程共同访问的临界资源 private int queueSize = 10;

private PriorityQueue queue = new PriorityQueue(queueSize);

// 获取Condition对象 Lock lock = new ReentrantLock();

Condition conditionProducer = lock.newCondition();

Condition conditionConsumer = lock.newCondition();

public static void main(String[] args) {

DemoCondition demoCondition = new DemoCondition();

Producer producer = demoCondition.new Producer();

Consumer consumer = demoCondition.new Consumer();

producer.start();

consumer.start();

}

class Producer extends Thread{

@Override

public void run() {

produce();

}

private void produce()

{

while (true)

{

lock.lock();

try

{

while (queue.size() == queueSize)

{

try {

System.out.println("队列已满,生产者线程需阻塞");

conditionProducer.await();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

System.out.println("队列未满,生产者线程可以生产一个商品");

// 模仿生产一个商品需要耗时1秒 try{

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

queue.offer(1);

System.out.println("生产者线程生产了一个商品");

// 通知消费者线程可以消费商品了 conditionConsumer.signalAll();

}

finally {

lock.unlock();

}

// 线程让步,使下一次操作可能是生产者生产商品,也可能是消费者消费商品 Thread.yield();

}

}

}

class Consumer extends Thread{

@Override

public void run() {

consume();

}

private void consume()

{

while (true)

{

lock.lock();

try

{

while (queue.isEmpty())

{

try {

System.out.println("队列为空,消费者线程需阻塞");

conditionConsumer.await();

} catch (InterruptedException e)

{

e.printStackTrace();

}

}

System.out.println("队列非空,消费者线程可以消费一个商品");

// 模仿消费一个商品需要耗时1秒 try{

Thread.sleep(2000);

} catch (InterruptedException e)

{

e.printStackTrace();

}

queue.poll();

System.out.println("消费者线程消费了一个商品");

// 通知生产者线程可以生产商品了 conditionProducer.signalAll();

}

finally {

lock.unlock();

}

// 线程让步,使下一次操作可能是生产者生产商品,也可能是消费者消费商品 Thread.yield();

}

}

}

}

结果如下:

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列已满,生产者线程需阻塞

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列非空,消费者线程可以消费一个商品

消费者线程消费了一个商品

队列为空,消费者线程需阻塞

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

队列未满,生产者线程可以生产一个商品

生产者线程生产了一个商品

...

实际运行时发现结果比较固定,都是生产者线程将队列塞满后,消费者线程再消费,且不将队列消费为空生产者线程不会生产,可以将生产者或者消费者线程里的Thread.yiled()换成Thread.sleep(),强行让另一个线程获得锁。

参考:Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition - Matrix海子 - 博客园​www.cnblogs.comCSDN-专业IT技术社区-登录​blog.csdn.netJava多线程之ReentrantLock与Condition - 平凡希 - 博客园​www.cnblogs.com3423cfd42cad5ef7bd4b381edbea217f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值