JAVA-多线程-生产者与消费者
介绍
生产者与消费者问题是多线程同步的一个经典问题。生产者和消费者同时使用一块缓冲区,生产者生产商品放入缓冲区,消费者从缓冲区中取出商品。我们需要保证的是,当缓冲区满时,生产者不可生产商品;当缓冲区为空时,消费者不可取出商品。
方法实现
方法一 wait()与notify()方法
wait()用在以下场合:
(1)当缓冲区满时,缓冲区调用wait()方法,使得生产者释放锁,当前线程阻塞,其他线程可以获得锁。
(2)当缓冲区空时,缓冲区调用wait()方法,使得消费者释放锁,当前线程阻塞,其他线程可以获得锁。
notify()用在以下场合:
(1)当缓冲区未满时,生产者生产商品放入缓冲区,然后缓冲区调用notify()方法,通知上一个因wait()方法释放锁的线程现在可以去获得锁了,同步块代码执行完成后,释放对象锁,此处的对象锁,锁住的是缓冲区。
(2)当缓冲区不为空时,消费者从缓冲区中取出商品,然后缓冲区调用notify()方法,通知上一个因wait()方法释放锁的线程现在可以去获得锁了,同步块代码执行完成后,释放对象锁。
代码实现
import java.util.LinkedList;
class ShareData1 {
// 最大容量
public static final int MAX_SIZE = 4;
// 存储媒介
public static LinkedList<Integer> list = new LinkedList<>();
// 生产者
public void product() {
synchronized (list) {
while (list.size() == MAX_SIZE) {
System.out.println("缓冲区已满,停止生产!");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(1);
System.out.println("生产者 " + Thread.currentThread().getName() + " 生产, 缓冲区容量为 " + list.size());
list.notify();
}
}
// 消费者
public void customer() {
synchronized (list) {
while (list.size() == 0) {
System.out.println("缓冲区已为空,停止消费!");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.removeFirst();
System.out.println("消费者 " + Thread.currentThread().getName() + " 消费,缓冲区容量为 " + list.size());
list.notify();
}
}
}
public class ProductAndCustomer {
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareData1 shareData = new ShareData1();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.product();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "AAA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.customer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "BBB").start();
}
}
输出
方法二 await()与signalAll()
(1)await():导致当前线程等待,直到其他线程调用该Condition的signal()或signalAll()方法唤醒该线程。
(2)signal():唤醒在此Lock对象上等待的单个线程。
(3)signalAll():唤醒在此Lock对象上等待的所有线程。
代码实现
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData2 {
// 最大容量
public static final int MAX_SIZE = 4;
// 存储媒介
public static LinkedList<Integer> list = new LinkedList<>();
//lock
private Lock lock = new ReentrantLock();
//条件变量
private Condition condition = lock.newCondition();
// 生产者
public void product() {
lock.lock();
try {
while (list.size() == MAX_SIZE) {
System.out.println("缓冲区已满,停止生产!");
condition.await();
}
list.add(1);
System.out.println("生产者 " + Thread.currentThread().getName() + " 生产, 缓冲区容量为 " + list.size());
condition.signalAll();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
// 消费者
public void customer() {
lock.lock();
try {
while (list.size() == 0) {
System.out.println("缓冲区已为空,停止消费!");
condition.await();
}
list.removeFirst();
System.out.println("消费者 " + Thread.currentThread().getName() + " 消费,缓冲区容量为 " + list.size());
condition.signalAll();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ProductAndCustomer2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareData2 shareData = new ShareData2();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.product();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "AAA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.customer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "BBB").start();
}
}
输出
方法三 Lock与Condition机制
Condition接口的await()和signal()是用来做同步的两种方法,它们的功能基本上和Object的wait()、nofity()相同,或者说可以取代它们,但是它们和Lock机制是直接挂钩的。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。
代码实现
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData3 {
// 最大容量
public static final int MAX_SIZE = 4;
// 存储媒介
public static LinkedList<Integer> list = new LinkedList<>();
// lock
public static Lock lock = new ReentrantLock();
// 仓库满的条件变量
public static Condition full = lock.newCondition();
// 仓库空的条件变量
public static Condition empty = lock.newCondition();
// 生产者
public void product() {
lock.lock();
while (list.size() == MAX_SIZE) {
try {
System.out.println("缓冲区已满,停止生产!");
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(1);
System.out.println("生产者 " + Thread.currentThread().getName() + " 生产, 缓冲区容量为 " + list.size());
// 唤醒其他生产者与消费者线程
full.signal();
empty.signal();
lock.unlock();
}
// 消费者
public void customer() {
lock.lock();
while (list.size() == 0) {
try {
System.out.println("缓冲区已为空,停止消费!");
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.removeFirst();
System.out.println("消费者 " + Thread.currentThread().getName() + " 消费,缓冲区容量为 " + list.size());
//唤醒其他生产者与消费者线程
full.signal();
empty.signal();
lock.unlock();
}
}
public class ProductAndCustomer3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareData3 shareData = new ShareData3();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.product();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "AAA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.customer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "BBB").start();
}
}
输出
方法四 BlockingQueue阻塞队列
如果向一个已经满了的队列中添加元素或者从空队列中移除元素,都将会导致线程阻塞,线程一直等待到有旧元素被移除或新元素被添加的时候,才能继续执行。符合这种情况的队列,称为阻塞队列。
put():向阻塞队列中添加一个元素,队列满时,自动阻塞。
take():从阻塞队列中取出一个元素,队列空时,自动阻塞。
代码实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class ShareData4 {
// 最大容量
public static final int MAX_SIZE = 4;
// 存储媒介
public static BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(MAX_SIZE);
// 生产者
public void product() {
while (queue.size() == MAX_SIZE) {
System.out.println("缓冲区已满,停止生产!");
}
try {
queue.put(1);
System.out.println("生产者 " + Thread.currentThread().getName() + " 生产, 缓冲区容量为 " + queue.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 消费者
public void customer() {
while (queue.size() == 0) {
System.out.println("缓冲区已为空,停止消费!");
try {
queue.take();
System.out.println("消费者 " + Thread.currentThread().getName() + " 消费,缓冲区容量为 " + queue.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class ProductAndCustomer4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareData4 shareData = new ShareData4();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.product();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "AAA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.customer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "BBB").start();
}
}