JDK1.5提供的Lock锁体系
synchronized是性能低效的,提供隐式的上锁解锁,而Lock接口 中提供能够由开发者实现自定义上锁解锁的显式锁,性能更高一些。
Lock接口的实现子类:
可重入锁ReentrantLock
可重入读写锁ReentrantReadWriteLock.ReadLock
Condition
实例化Lock对象
Lock ticketLock = new ReentrantLock() ;
上锁void lock()
try{
lock.lock()
//以下代码只有一个线程可以运行
...
}
finally{
lock.unlock();//不论是否有异常,都会释放锁
}
解锁void unlock();
void lockInterrupt()throws InputedException();
//响应中断锁
boolean tryLock();
// 非阻塞式响应中断能立即返回,获取锁返回true反之为false
boolean tryLock(long time,TimeUnit unit);
// 超时获取锁,在超时内或未中断的情况下能获取锁
Condition newCondition();
// 获取与lock绑定的等待通知组件,
public class LockTest {
public static void main(String[] args) {
Runnable runnable = new LockRunn();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class LockRunn implements Runnable {
private int tick = 10;
private Lock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();//加锁,一般将锁加在try块之前,解锁放在finally中
try {
if (this.tick > 0) {
System.out.println(Thread.currentThread().getName() + "剩余票数" + --this.tick);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}
到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。
ReentrantLock中所有方法实际上都是调用了器静态内部类Sync中的方法,而Sync继承了AbstractQueuedSynchronizer(AQS–简称同步器)。
ReentrantLock(独占式重入锁)详解
重入:表示能够对共享资源重复加锁,即当前线程再次获取锁时不会被阻塞。
重入的实现:如果该同步状态不为0,表示此同步状态已被线程获取,再判断持有同步状态的线程是否当前线程,如果是:同步状态+1并返回true,表示持有线程重入同步块。
释放过程:当且仅当同步状态减为0并且线程为当前线程时表示锁被正确释放。否则调用setState()将减1后的状态设置回去。
若非持有锁线程调用tryRelease()方法会抛出异常IllegalMonitorStateException
公平锁与非公平锁:
公平锁:锁的获取顺序符合时间顺序,即等待时间最长的线程最先获取锁.
要使用公平锁,调用ReentrantLock有参构造传入true,获取内置的公平锁.
公平锁的实现:利用tryAcquire()获取同步状态。首先判断当前队列是否有元素,若有,入队尾排队。
非公平锁的实现:首先会利用自旋尝试竞争锁,若失败调用tryAcquire(),tryAcquire()方法中又利用自旋争取资源。
非公平锁效率高于公平锁,因此默认选择非公平锁.
公平锁保证了获取到锁的线程一定是等待时间最长的线程,保证了请求资源时间上的绝对顺序,需要频繁的进行上下文切换,性能开销较大.
非公平锁保证系统有更大的吞吐量(效率较高),但会造成线程饥饿现象(有的线程可能永远无法获取非公平锁)
内建锁默认是独占锁,Lock锁拥有共享锁,响应中断和超时机制。
ReentrantReadWriteLock详解
读写锁:允许同一时刻被多个读线程访问,但在写线程访问时,所有的读线程与其他的写线程均会阻塞.
写线程能够获取到锁的前提条件:没有任何读、写线程拿到锁.
注:读锁不等于无锁,因为在写线程拿到锁后,读线程就不能进行。
写锁WriteLock(内部类)
写锁获取(模板方法-tryAcquire()):是独占锁
同步状态的低十六位是表示写锁,高十六位表示读锁。
写锁获取逻辑:当读锁已被读线程获取或写锁已被其他线程获取,则写线程失败;否则,当前同步状态没有被任何读写线程获取,当前线程获取写锁成功并且支持重入。
写锁释放逻辑同独占式锁的释放(release)逻辑
读锁–共享式锁(tryAcquireShared()),写锁是互斥的。
注:缓存的实现就是应用读写锁
锁降级:写锁可以降级为读锁,反过来读锁不能升级为写锁。
Condition接口的await()与signal机制
常用等待抽象方法:
void await()---同Object.wait(),直到被中断或唤醒
void awaitUninterrupt()---不响应中断,直到被唤醒。
boolean await(long time, TimeUnit unit)throws InterruptedException---同Object.wait(long timeout),多了自定义时间单位,中断、超时、被唤醒
boolean awaitUntil(Date deadline) throws InterruptedException 支持设置截至时间
常用唤醒抽象方法:
void signal():唤醒一个等待在Condition上的线程,将该线程由等待队列转移到同步队列中。
void signalAll():将所有等待队列在condition上的线程全部转移到同步队列中。
与内建锁wait()、notify()的区别
1.Object提供的wait()与notify()方法是与对象监视器monitor配合完成线程等待与通知机制,属于JVM底层实现。而Condition与Lock配合完成的等待通知机制属于Java语言级别,具有更高的控制与扩展性。
2. Condition独有特性:1.支持不响应中断,而Object不支持。2.支持多个等待队列,而Object只有一个。3.支持截止时间设置,而Object不支持
condition的等待队列
等待队列与同步队列共享了Node节点类,等待队列是一个单向的带由头尾节点的队列。
condition应用场景:实现有界队列。
Condition实现生产消费者模型
public class ConditionPC {
public static void main(String[] args) {
GoodsC goodsC = new GoodsC(20);
Produce produce = new Produce(goodsC);
Consumers consumers = new Consumers(goodsC);
List<Thread> list = new ArrayList<>();
//启动10个消费者线程
for(int i=0;i<10;i++){
Thread thread=new Thread(consumers,"消费者"+i);
list.add(thread);
}
//启动5个消费者
for(int i=0;i<5;i++){
Thread thread=new Thread(produce,"生产者"+i);
list.add(thread);
}
//一键启动
for(Thread th:list){
th.start();
}
}
}
class GoodsC{
private String name;
private int count;
private int maxCount;
private Lock lock=new ReentrantLock();
//消费者等待队列
private Condition consumerCondition=lock.newCondition();
//生产者等待队列
private Condition producerConditon=lock.newCondition();
//设置商品名称
public void setGoods(String name){
lock.lock();
try {
//当商品达到数量最大值,生产者线程进入生产者等待队列
while (count == maxCount) {
try {
System.out.println(Thread.currentThread().getName()+"还有很多商品,歇会~");
producerConditon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
count++;
System.out.println(Thread.currentThread().getName()+"生产"+toString());
//唤醒处于消费队列的线程
consumerCondition.signalAll();
}finally {
lock.unlock();
}
}
public void getGoods(){
try{
lock.lock();
while (count==0){
System.out.println(Thread.currentThread().getName()+"还没有商品");
try {
consumerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName()+"消费"+toString());
//唤醒所有生产者线程
producerConditon.signalAll();
}finally {
}
}
@Override
public String toString() {
return "GoodsC{" +
"name='" + name + '\'' +
", count=" + count +
'}';
}
public GoodsC(int maxCount) {
this.maxCount = maxCount;
}
}
class Produce implements Runnable {
private GoodsC goodsC;
public Produce(GoodsC goodsC) {
this.goodsC = goodsC;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
this.goodsC.setGoods("纪梵希限量");
}
}
}
class Consumers implements Runnable{
private GoodsC goodsC;
public Consumers(GoodsC goodsC) {
this.goodsC = goodsC;
}
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true){
this.goodsC.getGoods();
}
}
}
AQS-同步器
同步器是用来构建锁(Lock体系)以及其他同步组件的基础框架,他的实现主要依赖一个int状态变量以及通过一个FIFO队列共同构成同步队列。
子类必须重写AQS用protected修饰的来改变同步状态的方法,其他方法主要是实现排队与阻塞机制。int状态的更新使用getState()、setState()以及compareAndSetState()。
子类推荐使用静态内部类来继承AQS实现自己的同步语义。同步器支持独占锁,也支持共享锁。
Lock与AQS的关系:锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作
任何一个实现了Lock接口的类一定实现了一个静态内部类继承了AQS,通过AQS的方法实现同步语义。
AQS的模板设计模式
AQS使用模板方法模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程
AQS提供的模板方法可以分为3类: 1. 独占式获取与释放同步状态; 2. 共享式获取与释放同步状态; 3. 查询同步队列中等待线程情况;
自己实现一个锁:
public class LockTest {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
for(int i=0;i<10;i++){
Thread thread=new Thread(()->{
try{
lock.lock();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
});
thread.start();
}
}
}
//自定义锁
class Mutex implements Lock{
private Sync sync=new Sync();
//AQS实现Lock的方法,自定义同步器
static class Sync extends AbstractQueuedSynchronizer {
@Override
//尝试获取锁
protected boolean tryAcquire(int arg) {
//0表示无锁
if(arg!=1){
throw new RuntimeException("arg参数不为1");
}
//CAS(0,1),若当前状态为0,则,用CAS将其置换为1,再把当前线程扔进去
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
//释放共享
protected boolean tryReleaseShared(int arg) {
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
//判断当前线程是否是持有线程
protected boolean isHeldExclusively() {
return getState()==1;
}
}
//----------------------------------------------------------------
@Override
public void lock() {
//调用acquire上锁
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,time);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
//------------------------------------------------------------------
}
AQS详解
在同步组件中(Lock锁),AQS最核心的部分,同步组件的实现依赖AQS通过的模板方法来实现同步组件语义。
AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等底层实现。
AQS核心组成:同步队列,独占锁的获取与释放,共享锁的获取与释放,可中断锁、超时锁。这一系列功能的实现依赖于AQS提供的模板方法。
独占锁的五个模板方法:
void acquire(int arg);
独占式获取同步状态,如果获取失败插入同步队列将进行等待。void acquireInterruptibly(int arg);
此方法可以在同步队列中响应中断boolean tryAcqireNanos(int args,long nanosTimeOut);
在2的基础上增加了超时功能,到了预计时间还未获得直接返回boolean tryAcquire(int arg);
获取锁成功返回true,否则返回falseboolean release(int arg);
释放同步状态,该方法会唤醒在同步队列的下一个节点
共享式锁的模板方法:
1.void acquireShared(int arg);
共享获取同步状态,同一时刻多个线程获取同步状态
2.void acquireSharedInterruptbly(int arg);
在1的基础上增加相应中断
3.boolean tryAcquireSharedNanos(int arg,long nanosTimeOut);
在2的基础上增加超时等待
4.boolean releaseShared(int arg);
共享式释放同步状态
同步队列
在AQS内部有一个静态内部类Node,这是同步队列中每个具体的节点.
节点中有如下属性:
int waitStatus:
节点状态
Node prev:
同步队列中的前驱节点
Node next:
同步队列中的后继节点
Thread thread:
当前节点包装的线程对象
Node nextWaiter:
等待队列中的下一个节点
节点状态值如下:
int INTINL=0//初始状态
int CANCELLED=1//当前节点从同步队列中取消
int SIGNAL=-1; // 后继节点处于阻塞状态.如果当前节点释放同步状态会通知后继节点,使节点继续运行
int CONDITION=-2;//节点处于等待队列,其他线程对Condition调用signal方法后,该节点会从等待队列移到同步队列中
int PROPAGATE=-3//共享式状态会无条件的传播
AQS同步队列采用带有头尾节点的双向链表
独占锁的获取:acquire(int arg);
获取锁失败后调用AQS提供的acquire(int arg)模板
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter()总结:
线程获取失败,将线程调用addWaiter()封装成Node进行入队操作。addWaiter()中方法enq()完成对同步队列的头节点初始化以及CAS尾插失败后的重试处理
acquireQueued()总结:节点入队后排队获取同步状态
1.当前节点前驱为头节点并且调用tryAcquire()再次获取同步状态成功(前驱节点出队),当前线程获得锁成功
2.如果当前获取锁失败,先不断自旋将前驱节点状态置为SIGNAL,而后调用LockSupport.park()方法将当前线程阻塞
shouldParkAfterFailedAcquire(p,node)总结:节点在同步队列中获取锁,失败后调用shouldParkAfterFailedAcquire,此方法主要逻辑是使用CAS将前驱节点状态置为SIGNAL,表示需要将当前节点阻塞。如果CAS失败,不断自旋,直到前驱节点状态置为SIGANL为止。
parkAndCheckInterrupt():将当前节点调用LockSupport.park()阻塞
独占锁的释放:release()
unlock()方法实际调用AQS提供的release()模板方法
releas方法实现:是unlock()方法的具体实现,首先获取头节点的后继节点,当后继节点不为null,会调用LockSupport.unpark()方法唤醒后继节点包装的线程。因此每一次释放锁释放后就会唤醒队列中该节点的后继节点所包装的线程。
独占式锁的释放与获取的总结
获取:
1.线程获取失败,将线程调用addWaiter()封装成Node进行入队操作。addWaiter()中方法enq()完成对同步队列的头节点初始化以及CAS尾插失败后的重试处理。
2.入队之后排队获取锁的核心方法:acquireQueued(),节点排队获取锁是一个自旋过程。当且仅当当前节点的前驱节点为头节点并且成功获取同步状态时,节点出队并且该节点引用的线程获取到锁。否则,不满足条件时会不断自旋将前驱节点的状态设置为SIGNAL而后调用LockSupport.park()将当前线程阻塞。
释放:
释放锁时会唤醒后继节点(后继节点不为空)
独占锁特性
获取锁时响应中断。获取锁响应中断与acquire()几乎一样,唯一区别在于当parkAndCheckInterrupt()返回true时表示线程阻塞时被中断,抛出中断异常后线程退出。
超时等待获取锁:
tryAcquireNanos(),该方法在三种情况下会返回:
1.在超时时间内,当前线程成功获取到锁。
2.当前线程在超时时间内被中断。
3.超时时间结束,仍未获取到锁,线程退出返回false
超时获取锁与可中断获取锁逻辑基本一致,唯一区别在于获取锁失败后,增加了一个时间处理,如果当前时间超过截取时间,线程不再等待,直接退出,返回false。否则将线程阻塞置为等待状态排队获取锁。
ThreadLocal
用于提高线程的局部变量,在多线程环境可以保证各个线程里的变量独立于其他线程,即ThreadLock可以为每个线程创建一个单独的副本,相当于线程的private static 类型变量,与同步机制恰好相反。保证多线程环境下数据的独立性。
public class Test {
//多线程共享
private static String staticValue;
//多线程独立
private static ThreadLocal<String> threadLocal =new ThreadLocal<>();
//jdk1.8提供的初始化方法
//private static ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(() -> "HELLO");
public static void main(String[] args) throws InterruptedException {
//main是主线程指向
//在主线程中修改staticValue
staticValue="主线程中的值";
threadLocal.set("主线程修改后的值");
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
staticValue="子线程中的值";
threadLocal.set("子线程修改后的值");
System.out.println(threadLocal.get());
}
},"子线程");
thread.start();
thread.join();
System.out.println(staticValue);//主线程中的值
System.out.println(threadLocal.get());//子线程修改后的值
}
}
ThreadLcoal实现方法:
1.set(T value)方法
set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不 为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。
ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。
2. get()方法
在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。
3. remove()方法
在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线 程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。
ThreadLocalMap详解
在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap, 如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程.
生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队 列来进行通讯,生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从 阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
与生产者消费者模型相关的两个方法: wait()与notify()方法
wait()-痴汉方法
wait()就是使线程停止运行,会释放对象锁。(运行态---->阻塞态)
1.wait()方法会使当前线程调用该方法后进行等待,并将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
2.wait()方法只能在同步方法或同步代码块中调用,如果调用wait()时没有适当的锁,会抛出异常。
3.wait()方法执行后,当前线程释放锁,其他线程可以竞争该锁。
wait()之后的线程继续执行有两种方法:
1.调用该对象的notify()方法唤醒等待线程
2.线程等待周期等待时调用interrupt()中断该线程
wait(long time)
:如果到了预计时间还没被唤醒,线程将继续执行。
notify()
1.notify()方法也必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑选一个线程唤醒。
2.notify()方法执行后,唤醒线程不会立即释放对象锁,要等待唤醒线程全部执行完毕后才释放对象锁。
notifyAll()唤醒所有在该对象上等待的线程
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被 阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等 待下一次被唤醒。
public class WaitTest {
public static void main(String[] args) {
//对象在主方法中创建,因为缓冲池只有一个
Object object=new Object();
Mythread waitThread=new Mythread(true,object);
Mythread notifyThread1=new Mythread(false,object);
Thread thread=new Thread(waitThread,"wait线程");
Thread thread2=new Thread(notifyThread1,"notify线程");
thread.start();
thread2.start();
System.out.println("main方法结束");
}
}
class Mythread implements Runnable {
private boolean flag;
private Object object;
public Mythread(boolean flag, Object object) {
this.flag = flag;
this.object = object;
}
public void waitMethod() {
synchronized (object) {
while (true) {
System.out.println("wait方法开始" + Thread.currentThread().getName());
try {
object.wait();
System.out.println("wait方法结束" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void notifyMethod() {
synchronized (object) {
System.out.println("notify方法开始" + Thread.currentThread().getName());
object.notify();
System.out.println("notify方法结束" + Thread.currentThread().getName());
}
}
@Override
public void run() {
if (flag) {
this.waitMethod();
} else {
this.notifyMethod();
}
}
}
线程阻塞的情况:
1.线程调用 sleep方法,主动放弃占用的处理器资源。
2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
4.线程等待某个通知。
5. 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法
生产者消费者模型
生产者与消费者不进行直接通信,而是通过阻塞队列间接通信,通过判断队列中商品,若是满了则生产者是否进行等待,队列中商品为空消费者提醒.
或是,当商品池空了消费者就进行等待,商品池有商品就提醒.
因为线程的同步是通过对对象加锁来控制的,因此生产者与消费者共同锁住同一个对象来进行"沟通".
public class PoducerCustomer {
public static void main(String[] args) {
Queue<Goods> goods=new LinkedList<>();
AtomicInteger au=new AtomicInteger(0);
Object monitor=new Object();
//创建生产者
Producer producer=new Producer(goods,monitor,au);
Producer producer2=new Producer(goods,monitor,au);
//创建消费者
Customer customer = new Customer(goods, monitor);
//创建线程启动生产者与消费者
new Thread(producer).start();
new Thread(producer2).start();
new Thread(customer).start();
}
}
class Goods{
private final String name;
public Goods(String name) {
this.name = name;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
'}';
}
}
class Producer implements Runnable{
//生产者将生产的商品放入缓冲池,
//因为缓冲池是共有的,因此不能new一个实例化对象,
// 只能通过传参的方式传入生产者的实例化对象中
private final Queue<Goods> goods;
// 利用对监听器对象上锁保证放入缓冲池的同步性
//因为生产者与消费者通过对同一个对象进行控制来进行沟通,
// 因此只能有一个实例化对象,在主类的主方法中产生
private final Object monitor;
//对商品进行编号,利用原子整型保证线程安全,在多线程中不能用i++
private final AtomicInteger atomicInteger;
public Producer(Queue<Goods> goods, Object monitor, AtomicInteger atomicInteger) {
this.goods = goods;
this.monitor = monitor;
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
while(true){
try {
//通过睡眠时间控制生产者与消费者的速度
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (monitor){
//设定缓冲池到达10个时,生产者wait()
//满等待
if(this.goods.size()==10){
try {
this.monitor.wait();
System.out.println("商品池满了,休息一会");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//若未满10个,向线程池中添加商品
Goods goods=new Goods(String.valueOf(atomicInteger.addAndGet(1)));
this.goods.add(goods);
System.out.println(Thread.currentThread().getName()+"生产"+goods);
}
}
}
}
}
class Customer implements Runnable{
//缓冲队列
private final Queue<Goods> goods;
private final Object monitor;
public Customer(Queue<Goods> goods, Object monitor) {
this.goods = goods;
this.monitor = monitor;
}
@Override
public void run() {
while (true){
try {
//通过睡眠时间控制生产者与消费者的速度
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (monitor){
//空唤醒
if(goods.isEmpty()){
System.out.println("商品池空了,快来生产呀~~");
monitor.notify();
}else {
System.out.println("消费者消费"+goods.poll());
}
}
}
}
}