java-多线程

发布时间 2023-11-17 02:57:14作者: 索静丨LLH

第十三章 多线程

程序,进程,线程

【1】程序,进程,线程

➢程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)

 

➢进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期  :  有它自身的产生、存在和消亡的过程 

 

➢线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。

  若一个进程同一时间并行执行多个线程,就是支持多线程的。

  

【2】单核CPU与多核CPU的任务执行:

 

  

【3】并行和并发:

并行:多个CPU同时执行多个任务

并发:一个CPU“同时”执行多个任务(采用时间片切换)

创建线程的三种方式

第一种:继承Thread类

【1】在学习多线程一章之前,以前的代码是单线程的吗?不是,以前也是有三个线程同时执行的。

 

【2】现在我想自己制造多线程---》创建线程 ??

线程类--》线程对象 

 1 package com.llh;
 2 
 3 /**
 4  * @author : msb-zhaoss
 5  * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 6  * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 7  */
 8 public class TestThread extends Thread{
 9     /*
10     一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
11     但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
12     然后线程的任务/逻辑写在run方法中
13      */
14     @Override
15     public void run() {
16         //输出1-10
17         for (int i = 1; i <= 10 ; i++) {
18             System.out.println(i);
19         }
20     }
21 }

 

 1 package com.llh;
 2 
 3 public class Test {
 4     //这是main方法,程序的入口
 5     public static void main(String[] args) {
 6         //主线程中也要输出十个数:
 7         for (int i = 1; i <= 10 ; i++) {
 8             System.out.println("main1-----"+i);
 9         }
10         //制造其他线程,要跟主线程争抢资源:
11         //具体的线程对象:子线程
12         TestThread tt = new TestThread();
13         //tt.run();//调用run方法,想要执行线程中的任务 -->这个run方法不能直接调用,直接调用就会被当做一个普通方法
14         //想要tt子线程真正起作用比如要启动线程:
15         tt.start();//start()是Thread类中的方法
16         //主线程中也要输出十个数:
17         for (int i = 1; i <= 10 ; i++) {
18             System.out.println("main2-----"+i);
19         }
20     }
21 }

运行结果:

  

设置读取线程名字

【1】setName,getName方法来进行设置读取:

 1 package com.llh;
 2 
 3 /**
 4  * @author : llh
 5  * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 6  * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 7  */
 8 public class TestThread extends Thread{
 9     /*
10     一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
11     但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
12     然后线程的任务/逻辑写在run方法中
13      */
14     @Override
15     public void run() {
16         //输出1-10
17         for (int i = 1; i <= 10 ; i++) {
18             System.out.println(this.getName()+i);
19         }
20     }
21 }
 1 package com.llh;
 2 
 3 /**
 4  * @author : llh
 5  * 测试类
 6  */
 7 public class Test {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         //给main方法这个主线程设置名字:
11         //Thread.currentThread()作用获取当前正在执行的线程
12         Thread.currentThread().setName("主线程");
13         //主线程中也要输出十个数:
14         for (int i = 1; i <= 10 ; i++) {
15             System.out.println(Thread.currentThread().getName()+"1-------"+i);
16         }
17         //制造其他线程,要跟主线程争抢资源:
18         //具体的线程对象:子线程
19         TestThread tt = new TestThread();
20         tt.setName("子线程");
21         //tt.run();//调用run方法,想要执行线程中的任务 -->这个run方法不能直接调用,直接调用就会被当做一个普通方法
22         //想要tt子线程真正起作用比如要启动线程:
23         tt.start();//start()是Thread类中的方法
24         //主线程中也要输出十个数:
25         for (int i = 1; i <= 10 ; i++) {
26             System.out.println(Thread.currentThread().getName()+"2-------"+i);
27         }
28     }
29 }

 

【2】通过构造器设置 名字:

 1 package com.llh;
 2 
 3 /**
 4  * @author : llh
 5  * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 6  * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 7  */
 8 public class TestThread extends Thread{
 9     public TestThread(String name){
10         super(name);//调用父类的有参构造器
11     }
12     /*
13     一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
14     但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
15     然后线程的任务/逻辑写在run方法中
16      */
17     @Override
18     public void run() {
19         //输出1-10
20         for (int i = 1; i <= 10 ; i++) {
21             System.out.println(this.getName()+i);
22         }
23     }
24 }

 

习题:买火车票

【1】原理:每个窗口都是一个线程对象:

  

【2】代码:

 1 package com.llh;
 2 
 3 public class BuyTicketThread extends Thread {
 4     public BuyTicketThread(String name){
 5         super(name);
 6     }
 7     //一共10张票:
 8     static int ticketNum = 10;//多个对象共享10张票
 9     //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
10     @Override
11     public void run() {
12         //每个窗口后面有100个人在抢票:
13         for (int i = 1; i <= 100 ; i++) {
14             if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
15                 System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
16             }
17         }
18     }
19 }
 1 public class Test {
 2     public static void main(String[] args) {
 3         //多个窗口抢票:三个窗口三个线程对象:
 4         BuyTicketThread t1 = new BuyTicketThread("窗口1");
 5         t1.start();
 6         BuyTicketThread t2 = new BuyTicketThread("窗口2");
 7         t2.start();
 8         BuyTicketThread t3 = new BuyTicketThread("窗口3");
 9         t3.start();
10     }
11 }

 

第二种:实现Runnable接口

【1】代码:

 1 package com.llh;
 2 
 3 /**
 4  * @author : llh
 5  * TestThread实现了这个接口,才会变成一个线程类
 6  */
 7 public class TestThread implements Runnable{
 8     @Override
 9     public void run() {
10         //输出1-10数字:
11         for (int i = 1; i <= 10 ; i++) {
12             System.out.println(Thread.currentThread().getName()+"----"+i);
13         }
14     }
15 }
 1 package com.llh;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5         //创建子线程对象:
 6         TestThread tt = new TestThread();
 7         Thread t = new Thread(tt,"子线程");
 8         t.start();
 9         //主线程里面也是打印1-10数字:
10         for (int i = 1; i <= 10 ; i++) {
11             System.out.println(Thread.currentThread().getName()+"---"+i);
12         }
13     }
14 }

 

运行结果:

  

习题:买火车票

【1】代码:

 

 1 public class BuyTicketThread implements Runnable {
 2     int ticketNum = 10;
 3     @Override
 4     public void run() {
 5         for (int i = 1; i <= 100 ; i++) {
 6             if(ticketNum > 0){
 7                 System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
 8             }
 9         }
10     }
11 }
 1 public class Test {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         //定义一个线程对象:
 5         BuyTicketThread t = new BuyTicketThread();
 6         //窗口1买票:
 7         Thread t1 = new Thread(t,"窗口1");
 8         t1.start();
 9         //窗口2买票:
10         Thread t2 = new Thread(t,"窗口2");
11         t2.start();
12         //窗口3买票:
13         Thread t3 = new Thread(t,"窗口3");
14         t3.start();
15     }
16 }

 

【2】实际开发中,方式1 继承Thread类   还是  方式2 实现Runnable接口这种方式多呢?--》方式2

(1)方式1的话有 Java单继承的局限性,因为继承了Thread类,就不能再继承其它的类了

(2)方式2的共享资源的能力也会强一些,不需要非得加个static来修饰

 

【3】Thread类 Runnable接口 有联系吗?

  

 

第三种:实现Callable接口

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,

但是这个run方法有不足:

 

(1)没有返回值

(2)不能抛出异常

 

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

 

实现Callable接口好处:(1)有返回值  (2)能抛出异常

缺点:线程创建比较麻烦

 

 1 package com.llh;
 2 
 3 import java.util.Random;
 4 import java.util.concurrent.Callable;
 5 import java.util.concurrent.ExecutionException;
 6 import java.util.concurrent.FutureTask;
 7 /**
 8  * @author : llh
 9  */
10 public class TestRandomNum implements Callable<Integer> {
11     /*
12     1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
13     2.如果带泛型,那么call的返回值就是泛型对应的类型
14     3.从call方法看到:方法有返回值,可以跑出异常
15      */
16     @Override
17     public Integer call() throws Exception {
18         return new Random().nextInt(10);//返回10以内的随机数
19     }
20 }
21 class Test{
22     //这是main方法,程序的入口
23     public static void main(String[] args) throws ExecutionException, InterruptedException {
24         //定义一个线程对象:
25         TestRandomNum trn = new TestRandomNum();
26         FutureTask ft = new FutureTask(trn);
27         Thread t = new Thread(ft);
28         t.start();
29         //获取线程得到的返回值:
30         Object obj = ft.get();
31         System.out.println(obj);
32     }
33 }

 

线程的生命周期

【1】线程声明周期:线程开始--》线程消亡

【2】线程经历哪些阶段:

 

 

线程常见方法

(1)start() :  启动当前线程,表面上调用start方法,实际在调用线程里面的run方法

(2)run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容

(3)currentThread :Thread类中一个静态方法:获取当前正在执行的线程

(4)setName 设置线程名字

(5)getName 读取线程名字

 

设置优先级

【1】同优先级别的线程,采取的策略就是先到先服务,使用时间片策略

【2】如果优先级别高,被CPU调度的概率就高

【3】级别:1-10   默认的级别为5

  

【4】代码:

 1 public class TestThread01 extends Thread {
 2     @Override
 3     public void run() {
 4         for (int i = 1; i <= 10; i++) {
 5             System.out.println(i);
 6         }
 7     }
 8 }
 9 class TestThread02 extends Thread{
10     @Override
11     public void run() {
12         for (int i = 20; i <= 30 ; i++) {
13             System.out.println(i);
14         }
15     }
16 }
17 class Test{
18     //这是main方法,程序的入口
19     public static void main(String[] args) {
20         //创建两个子线程,让这两个子线程争抢资源:
21         TestThread01 t1 = new TestThread01();
22         t1.setPriority(10);//优先级别高
23         t1.start();
24         TestThread02 t2 = new TestThread02();
25         t2.setPriority(1);//优先级别低
26         t2.start();
27     }
28 }

 

join

join方法:当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。

注意:必须先start,再join才有效。

 1 public class TestThread extends Thread {
 2     public TestThread(String name){
 3         super(name);
 4     }
 5     @Override
 6     public void run() {
 7         for (int i = 1; i <= 10 ; i++) {
 8             System.out.println(this.getName()+"----"+i);
 9         }
10     }
11 }
12 class Test{
13     //这是main方法,程序的入口
14     public static void main(String[] args) throws InterruptedException {
15         for (int i = 1; i <= 100 ; i++) {
16             System.out.println("main-----"+i);
17             if(i == 6){
18                 //创建子线程:
19                 TestThread tt = new TestThread("子线程");
20                 tt.start();
21                 tt.join();//“半路杀出个程咬金”
22             }
23         }
24     }
25 }

 

sleep

https://go.zbj.com/news/20146.html (段子)

【1】sleep : 人为的制造阻塞事件

 1 public class Test01 {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         try {
 5             Thread.sleep(3000);
 6         } catch (InterruptedException e) {
 7             e.printStackTrace();
 8         }
 9         System.out.println("00000000000000");
10     }
11 }

 

【2】案例:完成秒表功能:

 1 import javafx.scene.input.DataFormat;
 2 import java.text.DateFormat;
 3 import java.text.SimpleDateFormat;
 4 import java.util.Date;
 5 /**
 6  * @author : llh
 7  */
 8 public class Test02 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         //2.定义一个时间格式:
12         DateFormat df = new SimpleDateFormat("HH:mm:ss");
13         while(true){
14             //1.获取当前时间:
15             Date d = new Date();
16             //3.按照上面定义的格式将Date类型转为指定格式的字符串:
17             System.out.println(df.format(d));
18             try {
19                 Thread.sleep(1000);
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23         }
24     }
25 }

 

setDaemon

【1】设置伴随线程

将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要继续执行了

案例:皇上 --》驾崩 ---》妃子陪葬

 1 public class TestThread extends Thread {
 2     @Override
 3     public void run() {
 4         for (int i = 1; i <= 1000 ; i++) {
 5             System.out.println("子线程----"+i);
 6         }
 7     }
 8 }
 9 class Test{
10     //这是main方法,程序的入口
11     public static void main(String[] args) {
12         //创建并启动子线程:
13         TestThread tt = new TestThread();
14         tt.setDaemon(true);//设置伴随线程  注意:先设置,再启动
15         tt.start();
16         //主线程中还要输出1-10的数字:
17         for (int i = 1; i <= 10 ; i++) {
18             System.out.println("main---"+i);
19         }
20     }
21 }

 

结果:

  

stop

 1 public class Demo {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         for (int i = 1; i <= 100 ; i++) {
 5             if(i == 6){
 6                 Thread.currentThread().stop();//过期方法,不建议使用
 7             }
 8             System.out.println(i);
 9         }
10     }
11 }

 

线程安全问题

【1】出现问题:

(1)出现了两个10张票或者3个10张票:

 

(2)出现0,-1,-2可能:

  

 

上面的代码出现问题:出现了 重票,错票,---》 线程安全引起的问题 

原因:多个线程,在争抢资源的过程中,导致共享的资源出现问题。一个线程还没执行完,另一个线程就参与进来了,开始争抢。

解决:在我的程序中,加入“锁” --》加同步  --》同步监视器


方法1:同步代码块

【1】同步代码块演示1:

 1 public class BuyTicketThread implements Runnable {
 2     int ticketNum = 10;
 3     @Override
 4     public void run() {
 5         //此处有1000行代码
 6         for (int i = 1; i <= 100 ; i++) {
 7             synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率低 --》this就是这个锁
 8                 if(ticketNum > 0){
 9                     System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
10                 }
11             }
12         }
13         //此处有1000行代码
14     }
15 }

 

【2】同步代码块演示2:

 1 public class BuyTicketThread extends Thread {
 2     public BuyTicketThread(String name){
 3         super(name);
 4     }
 5     //一共10张票:
 6     static int ticketNum = 10;//多个对象共享10张票
 7     //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
 8     @Override
 9     public void run() {
10         //每个窗口后面有100个人在抢票:
11         for (int i = 1; i <= 100 ; i++) {
12             synchronized (BuyTicketThread.class){//锁必须多个线程用的是同一把锁!!!
13                 if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
14                     System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
15                 }
16             }
17         }
18     }
19 }

 

【3】同步监视器总结:

总结1:认识同步监视器(锁子)   -----  synchronized(同步监视器){ }

1)必须是引用数据类型,不能是基本数据类型

2)也可以创建一个专门的同步监视器,没有任何业务含义 

3)一般使用共享资源做同步监视器即可   

4)在同步代码块中不能改变同步监视器对象的引用 

 

5)尽量不要String和包装类Integer做同步监视器 

6)建议使用final修饰同步监视器

 

总结2:同步代码块的执行过程

1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码

2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open

3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态

4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open

5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)

强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close) 

 

总结3:其他

1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块 

2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

 

方法2:同步方法

【1】代码展示:

 1 public class BuyTicketThread implements Runnable {
 2     int ticketNum = 10;
 3     @Override
 4     public void run() {
 5         //此处有1000行代码
 6         for (int i = 1; i <= 100 ; i++) {
 7             buyTicket();
 8         }
 9         //此处有1000行代码
10     }
11     public synchronized void buyTicket(){//锁住的是this
12         if(ticketNum > 0){
13             System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
14         }
15     }
16 }
 1 public class BuyTicketThread extends Thread {
 2     public BuyTicketThread(String name){
 3         super(name);
 4     }
 5     //一共10张票:
 6     static int ticketNum = 10;//多个对象共享10张票
 7     //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
 8     @Override
 9     public void run() {
10         //每个窗口后面有100个人在抢票:
11         for (int i = 1; i <= 100 ; i++) {
12             buyTicket();
13         }
14     }
15     public static synchronized void buyTicket(){//锁住的  同步监视器: BuyTicketThread.class
16         if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
17             System.out.println("我在"+Thread.currentThread().getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
18         }
19     }
20 }

 

【2】总结:

总结1:

多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。

咱们的锁一般都是引用数据类型的。

目的:解决了线程安全问题。

 

总结2:关于同步方法

1) 不要将run()定义为同步方法

2) 非静态同步方法的同步监视器是this

    静态同步方法的同步监视器是 类名.class 字节码信息对象

3) 同步代码块的效率要高于同步方法

     原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部

4) 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块 

 

方法3:Lock锁

【1】Lock锁引入:

JDK1.5后新增新一代的线程同步方式:Lock锁

与采用synchronized相比,lock可提供多种锁方案,更灵活 

synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。

但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。

  

【2】代码演示:

 1 import java.util.concurrent.locks.Lock;
 2 import java.util.concurrent.locks.ReentrantLock;
 3 /**
 4  * @author : msb-zhaoss
 5  */
 6 public class BuyTicketThread implements Runnable {
 7     int ticketNum = 10;
 8     //拿来一把锁:
 9     Lock lock = new ReentrantLock();//多态  接口=实现类  可以使用不同的实现类
10     @Override
11     public void run() {
12         //此处有1000行代码
13         for (int i = 1; i <= 100 ; i++) {
14             //打开锁:
15             lock.lock();
16             try{
17                 if(ticketNum > 0){
18                     System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
19                 }
20             }catch (Exception ex){
21                 ex.printStackTrace();
22             }finally {
23                 //关闭锁:--->即使有异常,这个锁也可以得到释放
24                 lock.unlock();
25             }
26         }
27         //此处有1000行代码
28     }
29 }

 

【3】 Lock和synchronized的区别

        1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁

        2.Lock只有代码块锁,synchronized有代码块锁和方法锁

        3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

【4】优先使用顺序:

         Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)

 

线程同步的优缺点

【1】对比:

线程安全,效率低

线程不安全,效率高

 

【2】可能造成死锁:

死锁

>不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

>出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

 

【3】代码演示:

 1 public class TestDeadLock implements Runnable {
 2     public int flag = 1;
 3     static Object o1 = new Object(),o2 = new Object();
 4         
 5         
 6     public void run(){
 7         System.out.println("flag=" + flag);
 8         // 当flag==1锁住o1
 9         if (flag == 1) {
10             synchronized (o1) {
11                 try {
12                     Thread.sleep(500);
13                 } catch (Exception e) {
14                     e.printStackTrace();
15                 }
16                 // 只要锁住o2就完成
17                 synchronized (o2) {
18                     System.out.println("2");
19                 }
20             }
21         }
22         // 如果flag==0锁住o2
23         if (flag == 0) {
24             synchronized (o2) {
25                 try {
26                     Thread.sleep(500);
27                 } catch (Exception e) {
28                     e.printStackTrace();
29                 }
30                 // 只要锁住o1就完成
31                 synchronized (o1) {
32                     System.out.println("3");
33                 }
34             }
35         }
36     }
37         
38         
39     public static void main(String[] args) {
40         // 实例2个线程类
41         TestDeadLock td1 = new TestDeadLock();
42         TestDeadLock td2 = new TestDeadLock();
43         td1.flag = 1;
44         td2.flag = 0;
45         // 开启2个线程
46         Thread t1 = new Thread(td1);
47         Thread t2 = new Thread(td2);
48         t1.start();
49         t2.start();
50     }
51 }
 

【4】解决方法: 减少同步资源的定义,避免嵌套同步

 

线程通信问题

应用场景:生产者和消费者问题

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费

如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止

如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

   

代码结果展示:

  

代码:

1.商品:属性:品牌 ,名字

2.线程1:生产者

3.线程2:消费者

 

分解1

出现问题:

1.生产者和消费者没有交替输出

 

2.打印数据错乱

哈尔滨 - null

费列罗啤酒

哈尔滨巧克力

----没有加同步

 

代码展示:

 1 public class Product {//商品类
 2     //品牌
 3     private String brand;
 4     //名字
 5     private String name;
 6     //setter,getter方法;
 7     public String getBrand() {
 8         return brand;
 9     }
10     public void setBrand(String brand) {
11         this.brand = brand;
12     }
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19 }
 1 public class ProducerThread extends Thread{//生产者线程
 2     //共享商品:
 3     private Product p;
 4     public ProducerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
10             if(i % 2 == 0){
11                 //生产费列罗巧克力
12                 p.setBrand("费列罗");
13                 try {
14                     Thread.sleep(100);
15                 } catch (InterruptedException e) {
16                     e.printStackTrace();
17                 }
18                 p.setName("巧克力");
19             }else{
20                 //生产哈尔滨啤酒
21                 p.setBrand("哈尔滨");
22                 try {
23                     Thread.sleep(100);
24                 } catch (InterruptedException e) {
25                     e.printStackTrace();
26                 }
27                 p.setName("啤酒");
28             }
29             //将生产信息做一个打印:
30             System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
31         }
32     }
33 }
 1 public class CustomerThread extends Thread{//消费者线程
 2     //共享商品:
 3     private Product p;
 4     public CustomerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//i:消费次数
10             System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
11         }
12     }
13 }
 1 public class Test {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         //共享的商品:
 5         Product p = new Product();
 6         //创建生产者和消费者线程:
 7         ProducerThread pt = new ProducerThread(p);
 8         CustomerThread ct = new CustomerThread(p);
 9         pt.start();
10         ct.start();
11     }
12 }

 

分解2

【1】利用同步代码块解决问题:

 1 public class ProducerThread extends Thread{//生产者线程
 2     //共享商品:
 3     private Product p;
 4     public ProducerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
10             synchronized (p){
11                 if(i % 2 == 0){
12                     //生产费列罗巧克力
13                     p.setBrand("费列罗");
14                     try {
15                         Thread.sleep(100);
16                     } catch (InterruptedException e) {
17                         e.printStackTrace();
18                     }
19                     p.setName("巧克力");
20                 }else{
21                     //生产哈尔滨啤酒
22                     p.setBrand("哈尔滨");
23                     try {
24                         Thread.sleep(100);
25                     } catch (InterruptedException e) {
26                         e.printStackTrace();
27                     }
28                     p.setName("啤酒");
29                 }
30                 //将生产信息做一个打印:
31                 System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
32             }
33         }
34     }
35 }
 1 public class CustomerThread extends Thread{//消费者线程
 2     //共享商品:
 3     private Product p;
 4     public CustomerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//i:消费次数
10             synchronized (p){
11                 System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
12             }
13         }
14     }
15 }

 

【2】利用同步方法解决问题:

 1 public class Product {//商品类
 2     //品牌
 3     private String brand;
 4     //名字
 5     private String name;
 6     //setter,getter方法;
 7     public String getBrand() {
 8         return brand;
 9     }
10     public void setBrand(String brand) {
11         this.brand = brand;
12     }
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19     //生产商品
20     public synchronized void setProduct(String brand,String name){
21         this.setBrand(brand);
22         try {
23             Thread.sleep(100);
24         } catch (InterruptedException e) {
25             e.printStackTrace();
26         }
27         this.setName(name);
28         //将生产信息做一个打印:
29         System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
30     }
31     //消费商品:
32     public synchronized void getProduct(){
33         System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
34     }
35 }
 1 public class CustomerThread extends Thread{//消费者线程
 2     //共享商品:
 3     private Product p;
 4     public CustomerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//i:消费次数
10             p.getProduct();;
11         }
12     }
13 }
 1 public class ProducerThread extends Thread{//生产者线程
 2     //共享商品:
 3     private Product p;
 4     public ProducerThread(Product p) {
 5         this.p = p;
 6     }
 7     @Override
 8     public void run() {
 9         for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
10             if(i % 2 == 0){
11                 p.setProduct("费列罗","巧克力");
12             }else{
13                 p.setProduct("哈尔滨","啤酒");
14             }
15         }
16     }
17 }

 

(这个else中的代码在分解3中 演示了错误) 

 

分解3

【1】原理:

 

【2】代码:

 1 public class Product {//商品类
 2     //品牌
 3     private String brand;
 4     //名字
 5     private String name;
 6     //引入一个灯:true:红色  false 绿色
 7     boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
 8     //setter,getter方法;
 9     public String getBrand() {
10         return brand;
11     }
12     public void setBrand(String brand) {
13         this.brand = brand;
14     }
15     public String getName() {
16         return name;
17     }
18     public void setName(String name) {
19         this.name = name;
20     }
21     //生产商品
22     public synchronized void setProduct(String brand,String name){
23         if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
24             try {
25                 wait();
26             } catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29         }
30         //灯是绿色的,就生产:
31         this.setBrand(brand);
32         try {
33             Thread.sleep(100);
34         } catch (InterruptedException e) {
35             e.printStackTrace();
36         }
37         this.setName(name);
38         //将生产信息做一个打印:
39         System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
40         //生产完以后,灯变色:变成红色:
41         flag = true;
42         //告诉消费者赶紧来消费:
43         notify();
44     }
45     //消费商品:
46     public synchronized void getProduct(){
47         if(!flag){//flag == false没有商品,等待生产者生产:
48             try {
49                 wait();
50             } catch (InterruptedException e) {
51                 e.printStackTrace();
52             }
53         }
54         //有商品,消费:
55         System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
56         //消费完:灯变色:
57         flag = false;
58         //通知生产者生产:
59         notify();
60     }
61 }

 

【3】原理:

  

注意:wait方法和notify方法  是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)

注意:sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁

【4】线程生命周期完整图:

  

Loc锁情况下的线程通信

Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。 

 

它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition 

一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。 

Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。 

调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用 

  • Conditon中的await()对应Object的wait();
    • Condition中的signal()对应Object的notify();
  • Condition中的signalAll()对应Object的notifyAll()。

void await()  throws InterruptedException

造成当前线程在接到信号或被中断之前一直处于等待状态。

与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
    • 其他某个线程调用此 Condition 的 signalAll() 方法;或者
    • 其他某个线程中断当前线程,且支持中断线程的挂起;或者
    • 发生“虚假唤醒

在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。

void signal()

唤醒一个等待线程。

如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

void signalAll()

唤醒所有等待线程。

如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。    

 

更改代码:

 1 package com.llh;
 2 import java.util.concurrent.locks.Condition;
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 /**
 6  * @author : llh
 7  */
 8 public class Product {//商品类
 9     //品牌
10     private String brand;
11     //名字
12     private String name;
13     //声明一个Lock锁:
14     Lock lock = new ReentrantLock();
15     //搞一个生产者的等待队列:
16     Condition produceCondition = lock.newCondition();
17     //搞一个消费者的等待队列:
18     Condition consumeCondition = lock.newCondition();
19     //引入一个灯:true:红色  false 绿色
20     boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
21     //setter,getter方法;
22     public String getBrand() {
23         return brand;
24     }
25     public void setBrand(String brand) {
26         this.brand = brand;
27     }
28     public String getName() {
29         return name;
30     }
31     public void setName(String name) {
32         this.name = name;
33     }
34     //生产商品
35     public void setProduct(String brand,String name){
36         lock.lock();
37         try{
38             if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
39                 try {
40                     //wait();
41                     //生产者阻塞,生产者进入等待队列中
42                     produceCondition.await();
43                 } catch (InterruptedException e) {
44                     e.printStackTrace();
45                 }
46             }
47             //灯是绿色的,就生产:
48             this.setBrand(brand);
49             try {
50                 Thread.sleep(100);
51             } catch (InterruptedException e) {
52                 e.printStackTrace();
53             }
54             this.setName(name);
55             //将生产信息做一个打印:
56             System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
57             //生产完以后,灯变色:变成红色:
58             flag = true;
59             //告诉消费者赶紧来消费:
60             //notify();
61             consumeCondition.signal();
62         }finally {
63             lock.unlock();
64         }
65     }
66     //消费商品:
67     public void getProduct(){
68         lock.lock();
69         try{
70             if(!flag){//flag == false没有商品,等待生产者生产:
71                 try {
72                    // wait();
73                     //消费者等待,消费者线程进入等待队列:
74                     consumeCondition.await();
75                 } catch (InterruptedException e) {
76                     e.printStackTrace();
77                 }
78             }
79             //有商品,消费:
80             System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
81             //消费完:灯变色:
82             flag = false;
83             //通知生产者生产:
84             //notify();
85             produceCondition.signal();
86         }finally {
87             lock.unlock();
88         }
89     }
90 }