死锁及生产者消费者模型
1. 死锁
死锁参生的条件(下列四个条件必须同时满足):
1. 互斥
- 共享资源只能同时被一个线程占用
2. 占有且等待
- 拿到了一个锁,不释放的同时又去申请另一个锁
3. 不可抢占
- 线程不能强行抢占其他线程的锁
4. 循环等待
- 线程T1等待线程T2占有的资源,线程T2又等待线程T1占有的资源
解决死锁的思路:破坏参生条件的任意一个
银行家算法---->解决死锁问题
2. ThreadLocal
ThreadLocal----线程本地变量(属于线程私有资源,不与其他线程共享)
set()
设置线程私有属性值
get()
取得线程私有属性值
- 在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。
- 每个线程的
ThreadLocalMap
(存放元素)是属于线程自己的,ThreadLocalMap
中维护的值也是属于线程自己的。这就保证了ThreadLocal
类型的变量在每个线程中是独立的,在多线程环境下不会相互影响
3. Object类的 wait() 、noyify()
Object: wait() notify()必须搭配synchronized使用
使用wait(),notify()的前提: 必须在同步方法或同步代码块中使用(拿到相应对象的锁),如果没有synchronized
会抛出java.lang.IllegalMonitorStateException
(非法监视器状态异常)
wait():痴汉方法
持有锁的线程调用wait()
后会一直阻塞,直到有线程调用notify()
将其唤醒
wait()的重载方法:
public final native void wait(long timeout)
等待一段时间,若还未被唤醒,继续执行,默认单位为ms
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
synchronized (obj) {
System.out.println("wait开始...");
obj.wait(3000);
System.out.println("wait结束...");
}
}
}
不加唤醒时间
添加加唤醒时间
notify():
唤醒任意一个处于等待状态的线程(notify方法就是使停止的线程继续运行)
notify()唤醒等待的线程:
/**
* @Author: Mr.Q
* @Date: 2019-07-26 11:46
* @Description: notify()唤醒等待的线程
*/
class MyThread implements Runnable {
private boolean flag;
private Object obj;
public MyThread(boolean flag, Object obj) {
this.flag = flag;
this.obj = obj;
}
public void waitMethod() {
synchronized (obj) {
try {
while (true) {
System.out.println("wait()方法开始..."+ Thread.currentThread().getName());
obj.wait();
System.out.println("wait()方法结束!"+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void notifyMethod() {
synchronized (obj) {
System.out.println("notify()方法开始..."+ Thread.currentThread().getName());
obj.notify();
System.out.println("notify()方法结束!"+ Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag) {
this.waitMethod();
}else {
this.notifyMethod();
}
}
}
public class NotifyTest {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread waitThread = new MyThread(true, object);
MyThread notifyThread = new MyThread(false, object);
Thread thread1 = new Thread(waitThread, "wait线程");
Thread thread2 = new Thread(notifyThread, "notify线程");
thread1.start();
Thread.sleep(2000);
thread2.start();
System.out.println("main方法结束!!");
}
}
运行结果分析:
从结果上来看第一个线程执行的是一个waitMethod方法,该方法里面有个死循环并且使用了wait方法进入等待状态将锁释放,如果这个线程不被唤醒的话将会一直等待下去,这个时候第二个线程执行的是notifyMethod方法,该方法里面执行了一个唤醒线程的操作,并且一直将notify的同步代码块执行完毕之后才会释放锁然后继续执行wait结束打印语句。
任意一个Object及其子类对象都有来个队列:
- 同步队列:所有尝试获取该对象
Monitor
失败的线程,都加入同步队列,排队获取 - 等待队列:已经拿到了锁的线程在等待其他资源时,主动释放锁,置入该对象等待队列中,等待其被唤醒;当调用
notify()
会在等待队列中任意唤醒一个线程,将其置入到同步队列尾部,排队获取锁
notifyAll()
将等待队列中的所有线程唤醒,并且加入到同步队列
敲黑板:
既然学习了wait()
,它可以导致线程阻塞需要用notify()
或者notifyAll()
来唤醒,那么它和sleep()
有何区别呢?
- sleep()是Thread类中定义的方法,到了一定的时间后该线程自动唤醒,不会释放对象锁。
- wait()是Object类中定义的方法,要想唤醒必须使用notify()、notifyAll()方法才能唤醒,会释放对象锁
总结一下就是:所在的部门不同,唤醒的方法也不同
4. 生产消费者模型
- 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
- 这个阻塞队列就是用来给生产者和消费者解耦的。大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式
举一个生活中的例子来说明:
作为一个即将迈入大三的老学长,毕业后到“工地搬砖”一定会遇到租房子的问题。对于房东来说,我是一个消费者,她是房子的生产者(可能不太恰当,抽象的理解一哈哈啦)。我要租房子,一定得和房东协商,那么,这个效率就比较低了,如果这个房子我觉得不合适,只能再去看房子,再和其它的房东进行协商;而房东呐,她也只能等着房客来看房子。
但是,有一个机构它聪明呀,他可能偷偷看了“生产者消费者模型”,理解到了其中的真谛,于是,他作为“中介”的角色出现了…现在,他到各个房东手上收集房源,然后整理出来给租客们选择,然后闷声发大财。
那么,中介这个角色是不是就相当于“容器”,来解决生产者(房东)和消费者(租客)的强耦合问题。房客住的房子有问题了,找中介;房东想涨房租,找中介;中介来调和房东与房客之间的问题,不在需要房东与房客之间有联系。
代码试着来实现上述的逻辑:
//商品类
class Goods {
private String goodsName; //货物名称
private int count; //货物库存
//生产商品
public synchronized void set(String goodsName) {
this.goodsName = goodsName;
this.count = count+1;
System.out.println(Thread.currentThread().getName()+" 生产"+this);
}
//消费商品
public synchronized void get() {
this.count = this.count-1;
System.out.println(Thread.currentThread().getName()+" 消费"+this);
}
@Override
public String toString() {
return "Goods { " +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
/**
* 生产者类
*/
class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
this.goods.set("海景别墅一套,房租减半,水电全免...");
}
}
/**
* 消费者类
*/
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
this.goods.get();
}
}
public class ProducerConsumer {
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
Thread produceThread = new Thread(new Producer(goods),"生产者线程");
Thread consumeThread = new Thread(new Consumer(goods),"消费者线程");
produceThread.start();
Thread.sleep(1000);
consumeThread.start();
}
}
ATTENTION:
那么问题来了,将生产者线程启动和消费者线程启动的代码换个位置。此时问题产生了,生产者还没生产商品消费者就消费了导致数量不正确。此时就需要我们的主角 wait() 和 notify() 方法帮忙
我们只需要更改商品类即可:
//商品类
class Goods {
private String goodsName; //货物名称
private int count; //货物库存
//生产商品
public synchronized void set(String goodsName) throws InterruptedException {
if(this.count > 0) {
System.out.println("房源充足,等待租客...");
}
this.goodsName = goodsName;
this.count = count+1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" 生产"+this);
// 生产完商品后通知消费者线程可以消费了
notify();
}
//消费商品
public synchronized void get() throws InterruptedException {
this.count = this.count-1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" 消费"+this);
// 消费完商品后通知生产者线程可以生产了
notify();
}
}