等待与唤醒
什么是等待唤醒机制
它是一种多线程的协作机制,就是在一个线程进行了规定操作后,就进入等待状态wait()
等待其他其他线程执行完他们指定的代码后,再将其唤醒(notify()) 在有多个线程进行等待时候,
如果需要,可以使用notifyAll()来唤醒所有的线程等待
wait/notify就是线程间一种协作机制
等待唤醒的方法
- wait:线程不在活动,不在参与调度,进入wait set中,不会浪费cpu资源,也不会去竞争锁了,这个时候线程
的状态是waiting 它还要等待别的线程执行一个特别的动作 也就是通知notify 在这个对象上等待的线程从
waitset释放出来,重新进入调度队列(ready queue)中
2.notif:选取所通知对象的wait set中的一个线程释放
3 notifyAll: 释放所通知对象的waitset上的全部线程
注意:
哪怕只通知一个等待的线程,被通知线程也不能立即恢复执行.因为它当初终断多的地方是同步快内,而此刻它
已经不在持有锁 所以他需要再次尝试去获取锁(面临与其它线程竞争),成功后才能在当初调用wait方法之后的地方恢复执行
总结:
如果能获取锁,线程就从waiting状态变成runnable状态
否则,从wait set出来,又进入entry set,线程就从waiting状态又变成block状态
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
象调用的wait方法后的线程。 - wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
承了Object类的。 - wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方
法。
等待与唤醒机制其实就是:线程之间的通信
经典的生产者与消费者模式:
有效的资源(生产一个包子,吃一个包子,再生产一个,再吃)
通信:对包子的状态进行判断:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子
(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包
子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取
决于锁的获取情况
代码演示:
分析 :
需要哪些类
资源类: 包子类:
包子的属性:皮 馅儿
包子的状态:有为true,无:false
生产者(包子铺)类是一个线程类,可以继承thread
设置线程任务(run):生产包子
对包子的状态进行判断
true 有包子
包子铺调用wait方法进入状态
false: 没有包子
包子铺生产包子,生产好包子 修改包子的状态为true
唤醒吃货线程,让吃货线程吃包子
消费者: 吃货线程 继承线程thread
吃完后修改包子状态 且唤醒包子铺线程 生产包子
测试类:
main方法
创建包子对象
开启包子铺线程
吃货线程
代码:
public class Baozi{
//皮
String pi;
//馅儿
String xian;
//报纸的状态
BOOlean flag=false;
}
包子铺线程和包子铺线程关系 通信(互斥)
必须同时同步技术保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货的类需要把包子对象作为参数传递进来
需要在成员位置创建一个包子变量
使用带参数的构造方法 为包子变量赋值
public clas BaoziPu extends Thread{
//定义包子成员变量
private Baozi bz;
//构造方法
public Baozi (Baozi bz )
{
this.bz=bz;
}
// 设置线程任务:生产包子
@Overrider
public void run(){
int count=0;
//包子铺一直生产包子
while(ture){
synchronized(bz){
if(bz.flag==true){
//包子铺调用wait方法
try{
bz.wait()
}catch(InterruptedException e){
e.printStackTrace();
}
}
//被唤醒之后执行包子铺生产包子
//增加趣味性:交替生产包子
if(count%2==0){
//生产 薄皮三鲜馅儿包子
bz.pi="薄皮";
bz.xian="三鲜馅";
}else{
//另一种包子
}
count++;
打印(包子在生产);
//生产包子需要3 秒,有异常try catch
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
//包子铺生产好了
// 修改包子状态 唤醒吃货状态
bz.notify();
}
}
}
}
吃货线程
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag == false){//没包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
bz.flag = false;
bz.notify();
}
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
}
}
线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低
系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。
线程池原理:就是一个容器->集合(ArrayList Hashset,LinkedList,HashMap)
LinkedList 用这个最好
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内
存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
线程池:
Jdk1.5之后提供的
java.util.concurent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池
参数:
int nthreads:创建线程池中包含的数量
返回值:
ExecutorService接口,返回的是ExecotorService接口的实现类对象,我们可以使用ExecutorService接口接受(面向接口编程)
java.util.concurent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池方法 shutdown()
线程池的使用步骤:
参考代码:
1.线程池类
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ‐‐‐> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
2.Runnable实现类
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}