java-多线程
第十三章 多线程
程序,进程,线程
【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 }