生产者-消费者模型是一种多线程并发模型,其中生产者线程负责生产数据,并将数据放入共享的缓冲区中,而消费者线程则负责从缓冲区中取出数据并进行处理。
1.使用synchronize以及wait()、notify() /notifyAll()
Buffer类代表共享的缓冲区,它有produce和consume方法用于生产和消费数据。Producer和Consumer类分别代表生产者和消费者线程,它们通过调用Buffer的produce和consume方法来实现生产和消费的功能。通过synchronized关键字和wait/notify机制来进行线程同步。
public class ProducerConsumerExample {
public static void main(String[] args) {
Buffer buffer = new Buffer(2); // 共享的缓冲区大小为2
Thread producerThread = new Thread(new Producer(buffer));
Thread consumerThread = new Thread(new Consumer(buffer));
producerThread.start();
consumerThread.start();
}
}
在生产者方法produce()
中,当缓冲区已满时,生产者线程会进入等待状态,直到消费者线程消费了一个元素后才会被唤醒。同样,在消费者方法consume()
中,当缓冲区为空时,消费者线程会进入等待状态,直到生产者线程生产了一个元素后才会被唤醒。
synchronized (this)
是用来实现对象级别的同步。当一个线程进入了被synchronized
修饰的代码块或方法时,它会尝试获取该对象的锁。如果锁没有被其他线程占用,那么该线程将获取到锁并执行代码块中的内容。如果锁已经被其他线程占用,那么该线程将进入等待状态,直到获取到锁为止。
在这段代码中,synchronized (this)
锁住的是当前对象,即生产者和消费者共享的对象。当缓冲区为空时,消费者线程会进入while
循环中的wait()
方法,此时消费者线程会释放当前对象的锁,并进入等待状态,让出CPU资源。同时,由于当前对象上的锁被释放,生产者线程可以获取到这个锁,并继续执行生产者方法中的代码。生产者线程会在缓冲区为空的情况下执行生产者方法中的代码,将一个元素添加到缓冲区中,并且调用notify()
或notifyAll()
方法来唤醒正在等待的消费者线程。被唤醒的消费者线程会重新尝试获取当前对象的锁,一旦获取到锁,它将继续执行while
循环中的代码,检查缓冲区是否为空。如果缓冲区仍然为空,消费者线程会再次进入等待状态,释放锁,让出CPU资源。
这种对象级别的同步确保了生产者和消费者线程之间的互斥访问,避免了多个线程同时修改缓冲区可能导致的数据不一致性或竞态条件问题。同时,通过使用wait()
和notify()
方法,生产者和消费者线程之间实现了协作,确保在合适的时机唤醒等待的线程。
public class Buffer {
private LinkedList<Integer> list;
private int capacity;
public Buffer(int capacity) {
this.list = new LinkedList<>();
this.capacity = capacity;
}
public void produce(int value) throws InterruptedException {
synchronized (this) {
while (list.size() == capacity) {
wait();
}
list.add(value);
System.out.println("Produced " + value);
notify();
}
}
public int consume() throws InterruptedException {
synchronized (this) {
while (list.size() == 0) {
wait();
}
int value = list.removeFirst();
System.out.println("Consumed " + value);
notify();
return value;
}
}
}
public class Producer implements Runnable {
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 1; i <= 5; i++) {
try {
buffer.produce(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable {
private Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 5; i++) {
try {
buffer.consume();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.使用Lock,Condition的await和signal方法
Lock
和Condition
是Java中用于实现线程间通信和同步的高级工具。
Lock
接口提供了比synchronized
关键字更灵活和可扩展的锁机制。通过Lock
,我们可以实现更细粒度的锁控制,并且可以显式地获取和释放锁。例如,可以使用ReentrantLock
类来实现可重入锁。
Condition
接口是与Lock
关联的条件队列。它提供了类似于synchronized
中的wait()
和notify()
方法的功能,即等待和唤醒线程。通过Condition
,我们可以在特定条件下暂停线程的执行并在满足条件时重新启动线程。Condition
提供了await()
方法用于线程等待和signal()
方法用于线程唤醒
public class Buffer {
private LinkedList<Integer> list;
private int capacity;
private Lock lock;
private Condition notFull;
private Condition notEmpty;
public Buffer(int capacity) {
this.list = new LinkedList<>();
this.capacity = capacity;
this.lock = new ReentrantLock();
this.notFull = lock.newCondition();
this.notEmpty = lock.newCondition();
}
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (list.size() == capacity) {
notFull.await();
}
list.add(value);
System.out.println("Produced " + value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (list.size() == 0) {
notEmpty.await();
}
int value = list.removeFirst();
System.out.println("Consumed " + value);
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
}
3. 使用BlockingQueue阻塞队列方法
BlockingQueue是Java中的一种特殊的队列,它实现了一种线程安全的数据结构,可以用于在多线程环境下进行数据的安全传输。
阻塞队列有以下特点:
-
当队列为空时,从队列中获取元素的操作将会被阻塞,直到有元素被添加到队列中;
-
当队列已满时,向队列中添加元素的操作将会被阻塞,直到有空间可用;
-
支持多线程并发操作,保证线程安全;
-
提供了一些阻塞的方法,如put和take,可以实现线程的等待和唤醒机制。
BlockingQueue提供了多个实现类,常用的有:
-
ArrayBlockingQueue:基于数组实现的有界阻塞队列;
-
LinkedBlockingQueue:基于链表实现的有界或无界阻塞队列;
-
PriorityBlockingQueue:基于优先级堆实现的无界阻塞队列;
-
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待一个相应的删除操作。
使用BlockingQueue可以方便地实现生产者-消费者模式,生产者将数据放入队列,消费者从队列中取出数据进行处理,而无需关心线程的同步和协调问题。
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> buffer = new LinkedBlockingQueue<>(2); // 共享的缓冲区大小为2
Thread producerThread = new Thread(new Producer(buffer));
Thread consumerThread = new Thread(new Consumer(buffer));
producerThread.start();
consumerThread.start();
}
}
public class Producer implements Runnable {
private BlockingQueue<Integer> buffer;
public Producer(BlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 1; i <= 5; i++) {
try {
buffer.put(i);
System.out.println("Produced " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable {
private BlockingQueue<Integer> buffer;
public Consumer(BlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 5; i++) {
try {
int value = buffer.take();
System.out.println("Consumed " + value);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}