2021年8月18日

今天的内容【很重要,但很难】

1.sleep() 让线程休眠一会儿

2.同步代码块【处理线程之间共享资源的】【线程安全】【重点!!难点!!!】

3.守护线程

4.死锁【开发中不用,但是面试会问!!!】

5.线程的生命周期

6.线程三个重要的方法

7.生产者消费者模式

1.关于sleep方法异常问题

package com.qfedu.a_thread;

class MyThread1 implements Runnable {

@Override
public void run() {
// TODO Auto-generated method stub
//以下的这个代码只有一个try-catch,没有throws
/**
* 是因为重写,开启了严格模式,
* 重写要求子类和父类的方法必须保持一致
* 在Runnable接口中,run方法后面没有throws没有抛,
* 所以子类也不能抛
*
* 在以后的开发中会遇到这种情况,得想通,为啥不能抛,只能try-catch
*
* */
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}
public class Demo2 {
public static void main(String[] args) {

}

}

2.同步代码块【难】

用来处理线程共享资源问题的。
共享机器:ATM机器,一个人在办理业务的时候,你进去锁门,锁住这个资源,其他人需要排队

厕所,三个坑位,我占着一个坑位,之后,后面得排队。如果不排队的话,
好几个人都抢到这个资源了,意味着一个坑位有好几个人在用!!!乱套了!!!!
一个坑位有好几个人在用!!!乱套了!!!!就是线程不安全的情况!!!

看电影需要买电影票:
《扫黑风暴》 【共享资源】
网络:淘票票   美团   猫眼

这里有三种订票方式。需要卖出去100张,通过三个渠道去卖这100张票,
100张票就是这三个渠道的共享资源,如果把三个渠道比作三个线程,这三个线程要去共享100张票。
卖票 ticket = 100
淘票票 第 99
美团 第99
猫眼 第99
线程的不安全性!!!

加锁!!!线程的同步
问题1:
这100张票该怎么保存,选择什么数据类型?
int 类型的数据
成员变量?创建三个线程,如果ticket是一个成员变量的话,
每一个销售线程ticket变量,ticket这个变量他就不是共享资源了。
pass
局部变量?定义在方法体内,定义在run方法中,每一次运行run方法,ticket都要重新定义一次,运行之后又销毁了,没有可持续性。
类变量?静态变量?可以
静态的成员变量,保存在数据区。可以提供给当前类对象使用,而且一处修改,处处修改!!!
package com.qfedu.a_thread;

class Sale1Ticket implements Runnable {
//这个代码是线程不安全的!!!
private static int ticket = 100;
/**
*
* synchronized的作用,只要被synchronized修饰以后,它所管辖的代码的
* 部分,要么全部执行,要么全部不执行。
* synchronized 可以修饰代码块,也可以修饰方法。
* 对代码块加锁的时候
* synchronized("锁") {
* 被锁的代码
* }
* 对方法进行加锁的时候
* synchronized(this) {
* 被锁的方法
* }
*
* */

@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//加完锁以后,if-else这个语句只能有一个线程,其他线程在外面排队
//等咱们进来的这个线程执行完以后吗,其他线程再抢占,抢到的再进来,
//z再一次进来的线程,也要把if语句执行完以后。再让其他线程进行抢占。
//就好比,你去找了一个坑位,三个人(线程)都在找这个坑位,如果不加锁
//三个人都可以进来共享坑位,乱套了。我个子大,我先抢到这个坑位。我抢到
//以后,我立马加锁,别人不能进,我在我自己坑位上面处理完以后,我出去别人再进。
//就会保证线程是安全的。
synchronized ("锁") {
if (ticket > 0) {

//可以使用线程锁的概念,线程同步的问题
try {
Thread.sleep(0);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+ "张票");

ticket--;

} else {
System.out.println("卖完了");
break;
}
}

}
}
}
public class Demo4 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Sale1Ticket(), "猫眼");
Thread thread2 = new Thread(new Sale1Ticket(), "美团");
Thread thread3 = new Thread(new Sale1Ticket(), "淘票票");
thread1.start();
thread2.start();
thread3.start();

}

}

不加锁,会导致资源不同步,线程不安全性。

加完锁以后,会解决线程不安全的问题。但是你的执行效率比不加锁的时候低

追求安全,不要追求效率了

卖火车票一样。

下去可以看看这个博客,多学点!!!

https://www.cnblogs.com/ysocean/p/6883729.html

理解至上,代码可以不会写,但是线程怎么走的必须得知道

3.守护线程

例如:

qq,内网通这些聊天软件,主程序叫非守护线程。而所有的聊天窗口线程叫守护线程。

如果主程序关闭,聊天窗口也就会关闭了。

主线程挂了,那么守护线程也随即挂了。

jvm,垃圾回收线程就是守护线程。当一个应用程序执行完毕,gc这个线程就停止了

守护线程就好比一个小喽喽,他的生死无关紧要,依赖整个主线程而运行的。但是起到的作用很大。

好比古代,皇帝死了以后,会有陪葬的。这个陪葬的就是守护线程

以后开发中,软件更新。日志更新,都是需要用到守护线程的。

package com.qfedu.a_thread;


class MyThread5 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("软件更新中........");
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("downloading:" + i + "%......");
}

}
}
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThread5());//自己定义的线程
//将Mythread5设置为守护线程 线程启动前必须调用此方法。
thread.setDaemon(true);
//设置完Thread5线程为守护线程以后,这个线程会随着主线程消亡而消亡。

thread.start();
for (int i = 0; i < 50; i++) {
Thread.sleep(100);
System.out.println("主线程在运行");
}



}

}

4.死锁

面试的时候会问!!!开发的时候千万别写死锁!!!

死锁是一种bug的存在

1.什么是死锁

2.死锁有什么危害

3.代码实现一个必然的死锁。

4.1什么是死锁

关键词:并发场景,多线程, 互不相让

死锁一定发生在并发场景,尤其是锁,目的是为了线程安全,物极必反。

死锁是一种状态,当两个线程互相持有对方的资源的时候,却又不主动释放自己手中的资源,导致大家都用不了了。线程无法继续往下执行。被称为死锁!!!

4.2写出一个死锁【java代码】

package com.qfedu.a_thread;


class DeadLockTask implements Runnable {
private boolean flag;//这个是变量
private Object lock1;
private Object lock2;
//以上是两个对象,锁住上面的额两个对象
//有参数的构造
public DeadLockTask(boolean flag, Object lock1, Object lock2) {
// TODO Auto-generated constructor stub
this.flag = flag;
this.lock1 = lock1;
this.lock2 = lock2;

}


@Override
public void run() {
// TODO Auto-generated method stub

if (flag) {
synchronized (lock1) {

System.out.println(Thread.currentThread().getName()+"拿到锁1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁2 的释放");

//这个lock1还没有释放出来呢,代码还没有执行完,等下一步的代码执行
synchronized (lock2) {
//这个代码没有打印
System.out.println(Thread.currentThread().getName()+ "->拿到锁2了");
}
}

}
if(!flag) {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "拿到锁2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁1的释放");

synchronized (lock1) {
//这个代码也咩有打印
System.out.println(Thread.currentThread().getName()+"拿到锁1");
}
}
}
}

}
public class Demo6 {

public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadLockTask(true, lock1, lock2)).start();
new Thread(new DeadLockTask(false, lock1, lock2)).start();
}
}

以上的代码看看就行了,千万不要模仿着去写,开发中这种情况是禁止的,不能写死锁

线程1锁lock1 但是想申请lock2。这个时候线程2已经获得了lock2这个锁对象了,在释放lock2之前得获取lock1对象,形成了一个闭环,陷入死锁循环

找了开锁公司,给你卡一个箱子。但是开锁需要证明身份。但是你证明身份的东西在箱子里面。 开锁公司:你先证明你的身份

你:证明身份东西在箱子里面,你打开之后我就能证明

开锁公司:有规定,你必须先证明你身份。

死锁!!!

5.线程的生命周期

1.创建线程

2.线程开启

3.可运行状态

4.运行状态

5.中间有可能阻塞 sleep() 死锁 只要你线程执行不下去,都算阻塞了。

6.线程的销毁

声明周期,就好比一个人的人生。

人先生出来,(人吃奶长大,上学,不挣钱)可运行状态,(工作 挣钱)运行状态

(失业了挣不了钱了)阻塞 ,人挂了。这个人的一生

线程生命周期

6线程的三个重要的方法

是Object下面的方法

wait()

调用该方法的线程处于等待状态【阻塞状态】,需要使用对象调用这个方法

而且这个对象必须是锁对象,wait方法一般和锁一起使用

notify()

唤醒线程,需要通过对象来调用,而且这个对象是锁对象

唤醒一个线程

notifyAll()

唤醒多个线程

明天我还会再讲这三个方法

package com.qfedu.b_waitnotify;

//声明一个类,然后这个类实例化以后,当成一个锁对象看待
public class Message {
private String message;
public Message () {

}
public Message(String message) {
this.message = message;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}

}
package com.qfedu.b_waitnotify;

//一个线程,这个线程有阻塞,需要使用noitify来唤醒的线程
public class Waiter implements Runnable{
private Message message;//要被锁住的对象,这个对象就是资源的共享
public Waiter(Message message) {
this.message = message;
}
@Override
public void run() {
// TODO Auto-generated method stub
String name =  Thread.currentThread().getName();//获取当前线程的名字
synchronized (message) {//message这个对象只能有一个线程能操作,
System.out.println(name + "等待唤醒的时间:"+ System.currentTimeMillis());
try {
message.wait();//线程走到这个地方,突然阻塞了,这个线程,突然暂停。
//这个wait方法 是需要另外一个线程来唤醒他,唤醒他以后,再记着执行下面的代码
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//下面这两行代码没有执行
System.out.println(name + "被唤醒的时间:" + System.currentTimeMillis());
System.out.println(name + "线程" + message.getMessage());
}
}

}
package com.qfedu.b_waitnotify;

//线程叫唤醒线程
//这个现成的目的是唤醒一个Waiter这个线程
//notify() 需要对象来用 加锁的是用一个对象,唤醒的时候,同一个对象来唤醒的
public class Notifier implements Runnable{
private Message message;
public Notifier(Message message) {
this.message = message;
}
@Override
public void run() {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();//获取当前线程的额名字
System.out.println(name + "唤醒");
try {
Thread.sleep(10000);//让唤醒线程先睡会儿,目的是为了让等待线程执行
synchronized (message) {
//将message对象属性message属性修改
message.setMessage("呵呵,还需要唤醒,菜");
//message.notify();//唤醒了另外一个线程,唤醒的是随机的
message.notifyAll();//唤醒所有的线程
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


}
package com.qfedu.b_waitnotify;

public class Demo1 {

	public static void main(String[] args) {
		Message message = new Message("我是message属性");
		Waiter waiter = new Waiter(message);
		Waiter waiter2 = new Waiter(message);
		new Thread(waiter2, "waiter2").start();//等待线程2
		new Thread(waiter, "waiter1").start();//等待线程1
		
		Notifier notifier = new Notifier(message);//唤醒线程
		new Thread(notifier, "notifier").start();
		
		
		//waiter1等待唤醒的时间:1629273542945
		//notifier唤醒
		//waiter1被唤醒的时间:1629273553012
		//waiter1线程呵呵,还需要唤醒,菜

		//1.首先实例化了等待线程,打印了等待线程+时间
		//2.这个等待线程碰到了wait()的方法,这个等待线程就阻塞
		//就暂停了,他下面的代码暂时不执行
		//3.唤醒线程抢到cpu了,System.out.println(name + "唤醒");
		//4.唤醒线程开始执行notify方法,是message调用的notify方法
		//5.同一个message对象,唤醒线程在执行,然后唤醒了等待线程
		//6.唤醒线程就会接着第2步往下执行
		/**
		 * 
		 * 好比:
		 * 	你是一个等待线程:写的有wait方法
		 * 		本来在上线下课,疫情了,会发生阻塞,接到政府通知,不能出门
		 * 政府是一个唤醒线程:写了notify方法
		 * 		说疫情结束了,小区解封,给你一个通知,我才能出门继续给大家
		 * 上线下课
		 * 
		 * 总结:最起码的两个线程:一个等待线程  wait()
		 * 						另外一个肯定是唤醒线程  notify()		
		 * 		这两个方法必须一起使用,不然你写的代码毫无意义,代码不执行了
		 * 
		 * */
	}
}

https://www.cnblogs.com/weibanggang/p/9470718.html

明天继续三个方法 wait() notify() notifyAll()

生产者消费者模式【难】以后会将一个框架 RabbitMQ 底层是生产者消费者模式



posted @ 2021-08-18 22:39  张三疯321  阅读(64)  评论(0)    收藏  举报