目录
信号量实现(Semaphore)
- 使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品
- 缓冲区属于临界资源,因此需要使用一个互斥量
mutex
来控制对缓冲区的互斥访问。 - 为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:
empty
记录空缓冲区的数量,full
记录满缓冲区的数量。
public class DemoSemaphore {
public static void main(String[] args) {
Item item = new Item();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
item.produce();
}
}, "生产者").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
item.consume();
}
}, "消费者").start();
}
}
class Item {
// 仓库容量
private final int capacity = 10;
// 互斥量,只能为 0或 1
Semaphore mutex = new Semaphore(1);
// 当 empty 不为 0 才可以放进物品(生产者)
Semaphore empty = new Semaphore(capacity);
// 当 full 不为 0 才可以取走物品(消费者)
Semaphore full = new Semaphore(0);
// 仓库中商品的数量
int count = 0;
public void produce() {
try {
empty.acquire();
mutex.acquire();
count++;
System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.release();
full.release();
}
}
public void consume() {
try {
full.acquire();
mutex.acquire();
count--;
System.out.println(Thread.currentThread() + "消费后剩余的商品数" + count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.release();
empty.release();
}
}
}
注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 mutex.acquire()
再执行 empty.acquire()
。
如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 empty.acquire()
操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 empty.release()
操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。
管程实现(Synchronized)
public class DemoSynchronized {
public static void main(String[] args) {
Item item = new Item();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
item.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
item.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者").start();
}
}
class Item {
// 仓库容量
private final int capacity = 10;
// 仓库中商品的数量
int count = 0;
public synchronized void produce() throws InterruptedException {
while (count == capacity) {
wait();
}
count++;
System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
notify();
}
public synchronized void consume() throws InterruptedException {
while (count == 0) {
wait();
}
count--;
System.out.println(Thread.currentThread() + "消费后剩余的商品数" + count);
notify();
}
}
JUC实现(ReentrantLock
+ Condition)
public class DemoReentrantLock {
public static void main(String[] args) {
Item item = new Item();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
item.produce();
}
}, "生产者").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
item.consume();
}
}, "消费者").start();
}
}
class Item {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 仓库容量
private final int capacity = 10;
// 仓库中存放商品的数量
int count = 0;
public void produce() {
lock.lock();
try {
while (count == capacity) {
condition.await();
}
count++;
System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + count);
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (count == 0) {
condition.await();
}
count--;
System.out.println(Thread.currentThread() + "消费后剩余的商品数" +count);
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
BlockingQueue
实现
public class DemoBlockingQueue {
// 相当于仓库容量为 5,仓库中还没有放商品
private static final BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
queue.put("放入商品");
System.out.println(Thread.currentThread() + "生产后仓库里的商品数" + queue.size());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
queue.take();
System.out.println(Thread.currentThread() + "消费后剩余的商品数" + queue.size());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数0
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
Thread[生产者,5,main]生产后仓库里的商品数1
Thread[消费者,5,main]消费后剩余的商品数0
输出结果来看不正确,这是因为 put()
或者 take()
操作和 sout
操作之间不是原子的,所以线程是会被抢占,造成查询其容量不正确,但是阻塞队列实现的生产者消费者肯定是线程安全的
Condition实现按序消费
/**
*要求,A执行完唤醒B,B执行完唤醒C,C执行完唤醒A,顺序执行,不可以乱序
*/
public class DemoCondition {
public static void main(String[] args) {
ABC abc = new ABC();
new Thread(()->{for (int i = 0; i < 10; i++) abc.printA(); },"A").start();
new Thread(()->{for (int i = 0; i < 10; i++) abc.printB(); },"B").start();
new Thread(()->{for (int i = 0; i < 10; i++) abc.printC(); },"C").start();
}
}
class ABC{
private int num = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try {
while(num != 1){//线程A等待
condition1.await();
}
num = 2;
System.out.println(Thread.currentThread().getName()+"=>AAA");
condition2.signal();//唤醒线程B
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(num != 2){
condition2.await();
}
num = 3;
System.out.println(Thread.currentThread().getName()+"=>BBB");
condition3.signal();//唤醒线程C
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.lock();
}
}
public void printC(){
lock.lock();
try {
while(num != 3){
condition3.await();
}
num = 1;
condition1.signal();//唤醒线程A
System.out.println(Thread.currentThread().getName()+"=>CCC");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.lock();
}
}
}