多线程学习-03

线程通信

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。
该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时等待(wait),等到下次消费者消耗了缓冲区中的数据的时候,生产者才能被唤醒(notify),开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入等待(wait),等到生产者往缓冲区添加数据之后,再唤醒消费者(notify)。通常采用线程间通信的方法解决该问题。
依据线程间的通信方式解决这类的生产者和消费者的问题的模式,叫做生产者与消费者设计模式。
例如:

示例代码:如果一个生产者、一个消费者

package com.thread.communication;

public class TestCommunication {

	public static void main(String[] args) {
		HouseWare h = new HouseWare(10);
		Worker w = new Worker(h);
		Customer c = new Customer(h);
		new Thread(w).start();
		new Thread(c).start();
	}
}

class HouseWare {
	private int num;

	public HouseWare(int total) {
		this.num = total;
	}

	public void put() {
		num++;
		System.out.println("工人生产了一台电视机,现在库存为:" + num);
	}

	public void take() {
		num--;
		System.out.println("消费者买走了一台电视机,现在库存为:" + num);
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 50; i++) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 50; i++) {
			h.take();
		}
	}
}

问题:

线程安全问题
透支消费问题
仓库容量有限问题

解决办法(一):synchronized+wait+notify

 线程安全问题:同步
 透支消费问题:线程通信
 仓库容量有限问题:线程通信
wait() 与 notify() 和 notifyAll(),Java.lang.Object提供的这三个方法。
 1.public final void wait():该线程发布对此监视器的所有权(即释放锁)并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。 
 2.public final void notify():唤醒在此对象监视器(锁)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
 3.public final void notifyAll():唤醒在此对象监视器(锁)上等待(wait)的所有线程。
特别注意:
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
package com.thread.communication;

public class TestCommunication {

	public static void main(String[] args) {
		HouseWare h = new HouseWare();
		Worker w = new Worker(h);
		Customer c = new Customer(h);
		new Thread(w).start();
		new Thread(c).start();
	}
}

class HouseWare {
	private final int MAX = 10;
	private int num;

	public synchronized void put() {
		if(num>=MAX){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println("工人生产了一台电视机,现在库存为:" + num);
		this.notify();
	}

	public synchronized void take() {
		if(num<=0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println("消费者买走了一台电视机,现在库存为:" + num);
		this.notify();
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 50; i++) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 50; i++) {
			h.take();
		}
	}
}

解决办法(二):Lock+Condition

 线程安全问题:同步
 透支消费问题:线程通信
 仓库容量有限问题:线程通信
而现在用Lock时,用的锁是Lock对象。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。 
package com.thread.communication;

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

public class TestCommunication3 {

	public static void main(String[] args) {
		HouseWare h = new HouseWare();
		Worker w = new Worker(h);
		Customer c = new Customer(h);
		new Thread(w).start();
		new Thread(c).start();
	}
}

class HouseWare {
	private final int MAX = 10;
	private int num;
	private final ReentrantLock lock = new ReentrantLock(); 
	private final Condition full  = lock.newCondition(); //此处用一个Condition也可以,但为了语义更清晰,可以使用两个Condition对象
	private final Condition empty = lock.newCondition(); 

	public void put() {
		lock.lock();
		if(num>=MAX){
			try {
				full.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println("工人生产了一台电视机,现在库存为:" + num);
		empty.signal();
		lock.unlock();
	}

	public void take() {
		lock.lock();
		if(num<=0){
			try {
				empty.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println("消费者买走了一台电视机,现在库存为:" + num);
		full.signal();
		lock.unlock();
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true){
			h.take();
		}
	}
}

多个生产者与多个消费者

如果多个生产者,多个消费者,仍然会有
 透支消费问题
 仓库容量有限问题
package com.thread.communication;

public class TestCommunication2 {

	public static void main(String[] args) {
		HouseWare h = new HouseWare();
		Worker w1 = new Worker(h);
		Worker w2 = new Worker(h);
		Customer c1 = new Customer(h);
		Customer c2 = new Customer(h);
		new Thread(w1,"A").start();
		new Thread(w2,"B").start();
		new Thread(c1,"C").start();
		new Thread(c2,"D").start();
	}
}

class HouseWare {
	private final int MAX = 10;
	private int num;

	public synchronized void put() {
		if(num>=MAX){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println("工人"+Thread.currentThread().getName()+"生产了一台电视机,现在库存为:" + num);
		this.notify();
	}

	public synchronized void take() {
		if(num<=0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println("消费者"+Thread.currentThread().getName()+"买走了一台电视机,现在库存为:" + num);
		this.notify();
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.take();
		}
	}
}

解决办法(一):synchronized+wait+notifyAll

1、notify()改为notifyAll()
2、被唤醒后,再次判断当前条件是否满足,再执行业务代码
package com.thread.communication;

public class TestCommunication2 {

	public static void main(String[] args) {
		HouseWare h = new HouseWare();
		Worker w1 = new Worker(h);
		Worker w2 = new Worker(h);
		Customer c1 = new Customer(h);
		Customer c2 = new Customer(h);
		new Thread(w1,"A").start();
		new Thread(w2,"B").start();
		new Thread(c1,"C").start();
		new Thread(c2,"D").start();
	}
}
class HouseWare {
	private final int MAX = 10;
	private int num;

	public synchronized void put() {
		while(num>=MAX){//醒来后继续判断条件,不满足继续睡
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		//被唤醒后,结束该方法,重写调用该方法
		}
		num++;
		System.out.println("工人"+Thread.currentThread().getName()+"生产了一台电视机,现在库存为:" + num);
		this.notifyAll();
		
	}

	public synchronized void take() {
		while(num<=0){//醒来后继续判断条件,不满足继续睡
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		
		}
		num--;
		System.out.println("消费者"+Thread.currentThread().getName()+"买走了一台电视机,现在库存为:" + num);
		this.notifyAll();
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.take();
		}
	}
}

解决办法(二):Lock+Condition(可以指定专门的线程苏醒或者睡眠)可以是线程有顺序的执行

package com.thread.communication;

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

public class TestCommunication4 {

	public static void main(String[] args) {
		HouseWare h = new HouseWare();
		Worker w1 = new Worker(h);
		Worker w2 = new Worker(h);
		Customer c1 = new Customer(h);
		Customer c2 = new Customer(h);
		new Thread(w1,"A").start();
		new Thread(w2,"B").start();
		new Thread(c1,"C").start();
		new Thread(c2,"D").start();
	}
}

class HouseWare {
	private final int MAX = 10;
	private int num;
	private final ReentrantLock lock = new ReentrantLock(); 
	private final Condition full  = lock.newCondition(); 
	private final Condition empty = lock.newCondition(); 

	public void put() {
		lock.lock();
		while(num>=MAX){
			try {
				full.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println("工人"+Thread.currentThread().getName()+"生产了一台电视机,现在库存为:" + num);
		empty.signalAll();
		lock.unlock();
	}

	public void take() {
		lock.lock();
		while(num<=0){
			try {
				empty.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println("消费者"+Thread.currentThread().getName()+"买走了一台电视机,现在库存为:" + num);
		full.signalAll();
		lock.unlock();
	}
}

class Worker implements Runnable {
	private HouseWare h;

	public Worker(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true) {
			h.put();
		}
	}
}

class Customer implements Runnable {
	private HouseWare h;

	public Customer(HouseWare h) {
		super();
		this.h = h;
	}

	@Override
	public void run() {
		while(true){
			h.take();
		}
	}
}

单例模式(懒汉式)线程安全问题 

class Single3{
	private static Single3 instance;
	private Single3(){}
	public static Single3 getInstance(){
		if(instance==null){
			synchronized (Single3.class) {
				if(instance==null){
					instance = new Single3();
				}
			}
		}
		return instance;
	}
	/*//可以实现,但效率还可以提升
	public synchronized static Single3 getInstance(){
		if(instance==null){
			instance = new Single3();
		}
		return instance;
	}*/
}

猜你喜欢

转载自blog.csdn.net/weixin_43549578/article/details/83928481