(20-9-4)java学习内容

多线程的框架
在这里插入图片描述

学习内容:

多线程的等待唤醒机制的用法与多线程的通信技术

多线程的等待唤醒机制

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、CSDN 技术博客 1 篇

随堂笔记:随手记录易错点

  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 当消费者拿到的时候就会出现多消费的情况 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值