多线程的框架
多线程的等待唤醒机制的用法与多线程的通信技术
多线程的等待唤醒机制
1.什么是多线程的等待唤醒机制? 相当于对线程进行通信(多线程任务执行的切换) 如:多个线程对一个任务的不同任务进行切换(通信)
拿一个生活中的例子举例:
客户:打开美团(加锁) – 》客户点外卖(Thread0线程任务开始执行)–》客户进行等待并唤醒外卖小哥(wait – notify)
外卖小哥:等待收取订单状态(wait)-- 》收取订单(notify) --》 加速送货跑跑跑…(Thread1线程开启) --》美食终于送到了到了,外卖小哥开始等待下一单并敲门提醒客户要吃饭啦(wait–notify)
具体的代码实现:
public class Test1 { public static void main(String[] args) { Object obj = new Object(); //开启点餐线程 new Thread() { public void run() { synchronized (obj) { //点餐 System.out.println("打开美团--》下单汉堡两份不要辣"); //等待外卖小哥送餐 try {obj.wait();}catch(InterruptedException e) {} System.out.println("你在搞啥子,这么慢?下次要快"); } } }.start(); //开启外卖小哥线程 new Thread() { public void run() { synchronized (obj) { //等待客户点餐 try {Thread.sleep(20);}catch(InterruptedException e) {} System.out.println("...花了20分钟时终于做好,快马加鞭送货中....咚咚咚,汉堡来了,帅哥"); //唤醒客户 吃东西啦 obj.notify(); } } }.start(); } }输出结果为: 打开美团–》下单汉堡两份不要辣 …花了20分钟时终于做好,快马加鞭送货中…咚咚咚,汉堡来了,帅哥 你在搞啥子,这么慢?下次要快
注:因为美团客户端与美团外卖小哥属于同平台,美团的小哥不可能送饿了么的单。所以相当于加锁动作
2.多线程的等待唤醒机制有什么用? 更加好的处理多线程之间的任务,使得线程之间具有配合性。 需要这个线程启动的时候才让他启动,或者一个线程执行完毕才让下一个线程启动。
简单说就是多线程之间具有了通信性
3.如何使用多线程的等待唤醒机制? 首先了解等待唤醒机制的三个方法(Object中的方法) 1.wait(): 2.notify() 3.notifyAll() 用法: 1.定义在锁中(监视器) 2.先wait后notify
重点: 掌握wait notify方法的使用 难点: 理解wait notify 与锁(synchronized)的关系,在与多线程进行通信的时候它们之间是如何配合进行工作的。 思考与总结: ①.wait与sleep都是让线程处于冻结状态,区别 1.sleep 释放执行权 不释放锁 2.wait 释放执行权并同时释放锁。 ②.wait一定要定义在锁中,也就是先要有锁才能wait 3.必须要先wait后notify,顺序不能错 ③锁对象可以是任意的。说明监视器(锁 wait notify notifyAll)方法是Object中的方法(任意对象调用的方式一定定义在Object中)如
class Demo { String name; } class S implements Runnable { Demo d; S(Demo d) public void run() { synchronzied(d) { d.wart();//wait定义在锁中 .....代码块 d.notify } } }**
**
/*vlog视频ID:多线程之间的通信--等待唤醒机制 * * 代码功能: * 输出男生或者女生的姓名与性别,输出男生的姓名性别的时候,女生的输出则需要等待男生输出完毕(如此交替进行输出) * * * 代码思路: * 1.涉及多线程技术中的等待唤醒机制 * 2.如果num==0 就输出name=liang;sex=nan 否则就输出name=菲菲;sex=女女女女 * 3.加入boolean 进行判断资源(Resources)中是否有名字与姓名false则无,无则可以设置名字与姓名,唤醒输出线程 * 4.然后输出线程被唤醒进行输出 * * 代码难点 * * * * * */ public class ThreadTest4 { public static void main(String[] args) { Resources1 r = new Resources1(); Input1 in = new Input1(r); Output1 out =new Output1(r); new Thread(in).start(); new Thread(out).start(); } } class Resources1 { public String name; public String sex; boolean flag = false; } class Input1 implements Runnable { Resources1 r; Input1(Resources1 r) { this.r = r; } public void run() { //如果falg为真就执行 int num = 0; while (true) { synchronized (r) { if (r.flag) try {r.wait();}catch(InterruptedException e) {} if (num==0) { r.name = "liang" ; r.sex = "nan"; } else { r.name = "菲菲"; r.sex = "女女女"; } r.flag = true;//把标记改为真,让输出线程运行输出命令 r.notify();//唤醒输出线程 } num = (num+1)%2; } } } class Output1 implements Runnable { Resources1 r; Output1(Resources1 r) { this.r = r; } @Override public void run() { while (true) { synchronized (r) { if (!r.flag) try {r.wait();}catch(InterruptedException e) {} System.out.println("name:"+ r.name + ":" + "<-->sex:" + r.sex); r.flag = false;//输出完毕后改为假让输入线程进行拿到姓名与性别 r.notify();//唤醒输入线程 } } } } 输出结果为: name:菲菲:sex:女女女 name:liang:sex:nan name:菲菲:sex:女女女 name:liang:sex:nan name:菲菲:sex:女女女 ....由此可以看出多线程的等待唤醒机制的执行过程 ①判断姓名年龄的值是否为空–》②空就赋值–》③最后输出–唤醒–①②③…如此循环往复
将资源的任务封装在资源中
**将资源的任务封装在资源中 姓名 与输出--》名字姓名的私有化,在外部提供访问的方法保证了资源类的安全性)** public class ThreadTest$1 { public static void main(String[] args) { //创建资源 Resourcest r =new Resourcest(); //创建任务,开启线程。执行路径 Outputt out = new Outputt(r); Inputt in = new Inputt(r); new Thread(in).start(); new Thread(out).start(); } } class Resourcest { //资源里的属性要可控(需要安全性)私有化 private String name; private String sex; boolean flag = false; public synchronized void set(String name,String sex)//设置名字线程,变成同步函数 { this.name = name; this.sex = sex; if (flag) { try {this.wait();}catch(InterruptedException e){} } this.flag = true; this.notify(); } public synchronized void out() //输出线程,同步函数 { if (!flag) { try {this.wait();}catch(InterruptedException e){} } System.out.println("name:"+name+":sex:"+sex); this.flag = false; this.notify(); } } class Inputt implements Runnable { Resourcest r; Inputt(Resourcest r) { this.r = r; } public void run() { int num = 0; while (true) { //加锁后进行判断真假值 if (num==0) { r.set("liang", "nan"); } else { r.set("菲菲", "女女女"); } num = ++num%2; } } } class Outputt implements Runnable { Resourcest r; public Outputt(Resourcest r) { this.r = r; } public void run() { while (true) { r.out(); } } }代码示例:
public class ThreadPcDemo { public static void main(String[] args) { /*原理基本上跟输出姓名性别的程序代码一致 * 不过换成了烤鸭 生产一只 就要消费一只 多了一个 count++ 变量 * 不同点就是生产消费需要记录数字 生产一只就消费一只 * * * * * */ Kaoya k = new Kaoya(); Production p = new Production(k); Consumption c = new Consumption(k); new Thread(p).start(); new Thread(c).start(); new Thread(p).start(); new Thread(c).start(); } } class Kaoya { private int count = 1; private String name; //为假就生产 为真就消费 boolean flag = false; //创建生产方法 public synchronized void Input(String name) { //进行标记的判断 while(flag) { try {this.wait();}catch(InterruptedException e) {} } //对this.anme 进行封装 以便更好地显示出来 this.name = name+count; System.out.println("生产了" + this.name + ":" + Thread.currentThread().getName()); this.flag = true; this.notifyAll(); count++; } // 创建消费方法 public synchronized void Output() { // while (!flag) try {this.wait();}catch(InterruptedException e) {} System.out.println("消费了:" + this.name + Thread.currentThread().getName()); this.flag = false; this.notifyAll(); } } class Production implements Runnable { Kaoya k; Production(Kaoya k) { this.k = k; } public void run() { while (true) { k.Input("烤鸭"); } } } class Consumption implements Runnable { Kaoya k; Consumption(Kaoya k) { this.k = k; } public void run() { while (true) { k.Output(); } } } * * *//*思考:为什么生产消费要用notifyAll?判断语句由if改为了while? --》而前面的线程通信输出 姓名,性别程序代码则不需要?
1.这次代码由于线程的增多 不再是单独的两个线程输出与输入了2个输入线程与2个输出线程假如在运行的时候输入线程(Input-Thread0)单纯的notify唤醒随机的一个线程,很有可能再次唤醒输入线程(Input-Thread1)导致这次线程没有输出,又进行了一次输入操作,就算运气好唤醒了输出线程也会导致程序做了大量的无用功(效率降低)。而输入线程 只用if(flag)判断的话 只会判断第一次,在运行时就相当于跳过了判断语句直接对线程任务进行操作,无需等待。这样就会导致了程序的不可控性。(安全性大大降低)简单说:1.不加notifyAll会导致程序效率低 2.不加while判断会导致安全性降低。(只限于一个任务两个以上的线程进行操作的时候,而单任务单线程无这些风险) 、 运行图解总结:
生产消费需要循环一遍 才会出错 (第一遍循环输出线程还执有线程的执行权 所以必定唤醒的是输入线程的方法)不用while判断标记会导致多生产多消费的情况出现:if只判断一次(会导致下次被唤醒不判断标记直接进行生产) while判断多次就没有这个问题,为false就等待
不用notifyAll会导致死锁 原因:notify会随机唤醒一个线程,如输入继续唤醒的是本类线程唤醒两次输入线程–输入线程(Thread0 Thread1)开始等待,然后输出线程继续随机连续唤醒两次(Thread2 Thread3) 输出线程也处于了冻结状态,自此4个线程全部处于了冻结状态。导致了死锁的发生。 *
学习时间: 8:00-11:30 12:30-13:00 14:00-19:00 20:00-21:00 晚上安排时间进行前面学习内容的复习
明日暂定学习内容: 1.剩下的多线程生产与消费者进行总结,易错难点进行思考() 2.多线程的守护线程的学习与总结,易错难点进行思考(什么是守护线程,为什么要用守护线程,怎么使用守护线程) 3.多线程的其他方法join进行学习,总结易错难点思考(什么是join方法 为什么要用join方法 join怎么使用)
学习了三篇代码 1.等待唤醒机制 2.等待唤醒资源的优化 与安全性的保证 3.一个任务被多个线程操作的时候通信安全问题
今日学习内容: 1.(等待唤醒机制) 2.(等待唤醒机制的优化–》将资源的任务封装在资源中 姓名 与输出–》名字姓名的私有化,在外部提供访问的方法保证了资源类的安全性) 3.(经典多线程实例–生产者消费者的实例) 4.(经典多线程的实例–生产消费者的实例改良版)
总结 1、 技术笔记 1 遍 2、 技术博客 1 篇
随堂笔记:随手记录易错点
num++ 需要单独使用 不能 num = num++;会产生问题 2.同步函数的锁是this 所以函数内的调用对象也是 this.wait();保持对象的一致性while 控制的是重复循环判断 所以不需要括号要准确 while(flag) { try {this.wait();}catch(InterruptedException e) {} }
思考问题?为什么生产消费要用notifyAll --》而前面的线程通信 姓名 性别则不需要?
3.while(flag) { try {this.wait();}catch(InterruptedException e) {} } //对this.anme 进行封装 以便更好地显示出来
this.name = name+count; System.out.println("生产了" + this.name + ":" + Thread.currentThread().getName()); count++; this.name = name +count 放在开头的话就会重复,因为放再开头不会判断标记直接就会在Input就赋值 count=2 当消费者拿到的时候就会出现多消费的情况