最新要闻
- Pro版同款!荣耀Magic5至臻版影像泄露:5000万像素旗舰三摄
- 今日要闻!《LOL》英雄价格正式调整!全面下调
- 全球热议:理想L9车主实惨:白天打开星环模式灯 扣1分罚款100元?
- iPhone良品率不足50%也不怕 富士康又在印度设立新工厂
- 小鹏汽车欲靠P7“回血”:老款清库再降3.5万、新车下周上市
- 降价后真香了!特斯拉中国2月销量出炉:暴涨130%
- 9.98万起杀疯!比亚迪王朝系列2月销量超10万台:秦PLUS贡献3成
- 2月新能源汽车销量榜:比亚迪一家占比近4成 第三名暴走
- 世界资讯:委员蒋胜男谈35岁职场危机:根源是“996” 必须改变
- 今日讯!中国性能车!全新领克03 TCR赛车官图发布:售价超百万
- 快报:144MB缓存立大功 AMD锐龙7000X3D内存自由:4800都稳赢i9
- 环球观点:AMD RX 7900 XT价格全面雪崩:沦落到RTX 4070 Ti的级别
- 《旷野之息》发售6周年:续作《塞尔达传说:王国之泪》发布新预告
- 当前视点!中国围棋第一人易主!李轩豪超越柯洁 AI立大功
- 环球快讯:vivo推出“手语翻译官”应用:准确率可达80%以上
- 【全球独家】限制儿童支付金额方便了!微信青少年模式升级:一键开启上线
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
环球头条:多线程全面总结
1.单线程
单线程:只有一个线程,即CPU只执行一个任务(一个线程)
(资料图)
1 class Hero{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 public void show(){ 7 System.out.println(name + "。。。。。"); 8 } 9 }10 11 public class ThreadDemo {12 public static void main(String[] args) {13 Hero d1 = new Hero("亚瑟");14 Hero d2 = new Hero("妲己");15 Hero d3 = new Hero("貂蝉");16 d1.show();17 d2.show();18 d3.show();19 }20 }
结果:,多线程顺序可能会不一样
2.多线程
多线程:就是多个线程同时运行,一个CPU执行多个任务(线程)
- 优点:能让代码同时执行,可以大大提高效率
- 能让代码同时执行,可以大大提高效率
1.主线程
java中,main方法是程序的入口,所以 main 方法被称为:主方法
- 执行 main 方法中代码的线程,被称为:主线程
需要注意的地方有 2 点:
- main 方法中的代码都是有 main 线程执行的
- 在 main 方法中调用其他方法,那么其他方法中的代码也是由main线程执行
2.创建线程
除了主线程外,java允许我们自己创建线程,通常有 2 种方式:
- 继承Thread类
- 实现Runnable接口
1.Thread类
java中有一个专门描述线程的类:Thread,通过继承这个类可以创建自己的线程,比如:
1 //1. 继承Thread 2 class Hero extends Thread{ 3 String name; 4 Hero(String name){ 5 this.name = name; 6 } 7 //2. 复写run方法 8 @Override 9 public void run(){10 System.out.println(name + "。。。。。");11 }12 }13 14 public class ThreadDemo {15 public static void main(String[] args) {16 //3. 创建线程对象17 Hero yase = new Hero("亚瑟");18 Hero daji = new Hero("妲己");19 Hero diaochao = new Hero("貂蝉");20 //4. 调用start方法:启动线程,之后会自动执行 run 方法21 yase.start();22 daji.start();23 diaochao.start();24 }25 }
结果:
注意:想要启动一个线程,必须调用的是 start 方法,只是调用 run 方法并不会开启新的线程
启动线程方法start()和run()区别
在Java中启动线程有两种方式:调用start()方法和调用run()方法。它们的区别如下:
- 调用start()方法:会启动一个新线程,并在新线程中执行run()方法里面的代码。
- 调用run()方法:不会启动新线程,而是在当前线程中同步执行run()方法里面的代码。
- 只有调用start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。
2.实现Runnable接口
实现 java 中的 Runnable 接口并复写 run 方法,也能创建线程,比如:
1 //1. 实现Runnable接口 2 class Hero implements Runnable{ 3 String name; 4 Hero(String name){ 5 this.name = name; 6 } 7 //2. 实现run方法 8 @Override 9 public void run() {10 System.out.println(name + "。。。。。");11 }12 }13 public class ThreadDemo {14 public static void main(String[] args) {15 //3. 创建Thread对象时把Runnable对象作为参数扔进去16 Thread t1 = new Thread(new Hero("亚瑟"));17 Thread t2 = new Thread(new Hero("妲己"));18 Thread t3 = new Thread(new Hero("貂蝉"));19 //4.调用start方法,启动一个线程,会自动调用run方法20 t1.start();21 t2.start();22 t3.start();23 }24 }
结果:
通常在学习时,我们都用 ‘匿名内部类’创建线程,比如:
1 public static void main(String[] args) { 2 //使用 匿名内部类 创建线程 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("使用匿名内部类创建一个线程。。。。。。"); 7 } 8 },"yase"); 9 yase.start();10 }
实现 Runnable 接口和继承 Thread 比较:
- java不支持多继承,如果继承Thread,那么就不能继承其他类了
- java支持多实现,如果实现Runnable,不但可以实现其他接口,也可以继承其他类
工作中推荐使用 Runnable 的方式创建线程
3.线程名
为了区分不同的线程,默认情况下每个线程都有名称
1.获取线程名
通过调用 getName() 方法获取
结果:
2.主线程名字
主线程也是有名称的,但是我们无法使用 this.getName() 获取主线程的名称
- 想要获取主线程名称,得先获取主线程对象
Thread类中有一个静态方法:currentThread(),用来获取当前线程对象,比如:
结果:
推荐使用:Thread.currentThread().getName() 获取线程名称
3.设置线程名
如果对默认的名称不满意,也可以在创建对象的时候自定义线程名称
- 继承 Thread 类
代码:
1 class Hero extends Thread{ 2 String name; 3 //1. 增加一个 threadName 参数,作为线程名 4 Hero(String name,String threadName){ 5 //2. 调用super,把自定义名称扔给父类 6 super(threadName); 7 this.name = name; 8 } 9 @Override10 public void run(){11 //3. 通过Thread.currentThread().getName()获取线程名称12 System.out.println(name + "。。。。。"+Thread.currentThread().getName());13 }14 }15 16 public class ThreadDemo {17 public static void main(String[] args) {18 //4. 创建Thread实例对象,自定义线程名19 Hero d1 = new Hero("亚瑟", "心灵战士");20 Hero d2 = new Hero("妲己","女仆咖啡");21 Hero d3 = new Hero("貂蝉", "异域舞娘");22 d1.start();23 d2.start();24 d3.start();25 }26 }
结果:
2.实现Runnable接口
1 class Hero implements Runnable{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 @Override 7 public void run() { 8 System.out.println(name + "。。。。。"+Thread.currentThread().getName()); 9 }10 }11 public class ThreadDemo {12 public static void main(String[] args) {13 //创建Thread对象时候,直接把线程名称扔进去14 Thread t1 = new Thread(new Hero("亚瑟"), "心灵战士");15 Thread t2 = new Thread(new Hero("妲己"), "女仆咖啡");16 Thread t3 = new Thread(new Hero("貂蝉"), "异域舞娘");17 t1.start();18 t2.start();19 t3.start();20 }21 }
结果:
4.线程名好处
线程名在实际工作中很重要,有以下好处
- 调试方便:当程序运行出现问题时,如果每个线程都有自己的名称,可以快速定位问题
- 可读性提高:如果代码中存在多个线程,如果有名称可以使得代码更易于理解和维护。
- 日志记录方便:当出现问题时,如果线程有名称,根据日志可以更方便地识别每个线程的相关信息
例:
1 class Hero implements Runnable{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 @Override 7 public void run() { 8 //如果 name 为null,会报异常 9 if(this.name.length()>0){10 System.out.println("aaaaaaaaaaaa");11 }12 System.out.println(name + "。。。。。");13 }14 }15 public class Demo {16 public static void main(String[] args) {17 Thread t1 = new Thread(new Hero(null));18 Thread t2 = new Thread(new Hero("妲己"));19 Thread t3 = new Thread(new Hero("貂蝉"));20 //4.调用start方法,启动一个线程,会自动调用run方法21 t1.start();22 t2.start();23 t3.start();24 25 }26 }
上面代码中没有设置线程名,结果:
上图中虽然有默认的线程名,但是根据 Thread-0 很难定位到 Thread t1 = new Thread(new Hero(null)); 这句代码在什么地方
修改代码,设置线程名:
1 Thread t1 = new Thread(new Hero("亚瑟"), "心灵战士");2 Thread t2 = new Thread(new Hero("妲己"), "女仆咖啡");3 Thread t3 = new Thread(new Hero("貂蝉"), "异域舞娘");
结果:
4.多线程提高工作效率
示例:医生给病人看病,一个人需要10分钟,两个人就需要20分钟,如果两个医生,那么一共需要10分钟
示例:
1 class SickPerson{ 2 String name; 3 4 public SickPerson(String name){ 5 this.name = name; 6 } 7 } 8 9 public class ThreadTest {10 public static void main(String[] args) {11 //1. 利用数组模拟两个病人12 SickPerson[] arr = new SickPerson[]{new SickPerson("yase"), new SickPerson("daji")};13 14 Thread bianque = new Thread(new Runnable() {15 @Override16 public void run() {17 System.out.println("扁鹊开始看病。。。。");18 //2. 使用 for 循环,一个个的处理病人19 for(int i = 0;i结果:
开启两个线程
1 //用数组模拟两个病人 2 SickPerson yase = new SickPerson("yase"); 3 SickPerson daji = new SickPerson("daji"); 4 5 // SickPerson[] arr = new SickPerson[]{new SickPerson("yase"),new SickPerson("daji")}; 6 //创建内部类Runnable重写run方法,使用for循环,处理病人, 线程睡眠,接着执行 7 Thread bianque = new Thread(new Runnable() { 8 @Override 9 public void run() {10 System.out.println("扁鹊开始看病");11 System.out.println("给"+yase.name+"看病需要5秒");12 try {13 Thread.sleep(5000);14 } catch (InterruptedException e) {15 }16 }17 },"bianque");18 bianque.start();19 //开启第二个线程20 Thread huatuo = new Thread(new Runnable() {21 @Override22 public void run() {23 System.out.println("华佗开始看病");24 System.out.println("给"+daji.name+"看病要5秒");25 try {26 Thread.sleep(5000);27 } catch (InterruptedException e) {28 throw new RuntimeException(e);29 }30 }31 },"huatuo");32 huatuo.start();结果:
5.数据安全问题
1.前提
之前我们了解到,CPU是不断的切换线程执行的,当CPU从A线程切换到B线程时,A线程就会暂停执行,比如:
1 public void run() {2 //当线程执行到第一句代码时,CPU很可能就切换其他线程执行,那么当前线程就会暂停3 System.out.println(name + "。。。。。");4 System.out.println(name + "。。。。。");5 System.out.println(name + "。。。。。");6 }2.多线程操作数据
当多个线程同时操作同一个数据时候,可能产生数据安全问题
示例:
1 class Hero implements Runnable{ 2 //1. 定义一个静态变量,多个线程同时操作它 3 public static int num = 10; 4 @Override 5 public void run() { 6 while (true){// while中用true,这是死循环,谨慎使用,这里是为了演示效果 7 //2. run方法中,对num--,当num<=0时,跳出循环 8 if(num > 0){ 9 //sleep(5),让当前线程休眠5毫秒,此时CPU会执行其他线程10 try { Thread.sleep(5); } catch (InterruptedException e) {}11 num--;12 System.out.println(Thread.currentThread().getName() + "***********" + num);13 }else{14 break;15 }16 }17 }18 }19 public class ThreadDemo {20 public static void main(String[] args) {21 Hero hero = new Hero();22 Thread yase = new Thread(hero, "yase");23 Thread daji = new Thread(hero, "daji");24 //3. 开启两个线程操作num25 yase.start();26 daji.start();27 }28 }结果:
代码中,当 num>0 时,才会执行输出语句,但是却输出了负数
分析一下执行过程:
- 2个线程刚开始正常执行,都会执行num--。。。。。
- 当num=1时,假如 "yase" 先进入 if ,休眠5毫秒,这时 CPU 切换线程‘daji’进入 if
- ‘yase’休眠结束,执行num--,接着‘daji’睡醒后也执行num--,最终num=-1
- 注意:即使没有 sleep 语句,也可能输出负数,只不过概率太低
总结:一个线程在操作数据时,其他线程也参与运算,最终可能造成数据错误
解决:保证操作数据的代码在某一时间段内,只被一个线程执行,执行期间,其他线程不能参与,即使用synchronized关键字
6.线程同步
线程同步:就是让线程一个接一个的排队执行
- 同步:即一步一步,也是一个接一个的意思
java中提供 synchronized关键字用来实现同步,可以解决多线程时的数据安全问题
不过,使用 synchronized 的方式有很多种,我们一个一个解释
1.synchronized代码块
格式:synchronized(锁对象){
同步代码块
}
锁对象:可以理解为钥匙、通行证,只有线程拿到通行证后,才能执行 { } 中的代码
演示
使用 synchronized 修改 run 方法:
1 public static Object obj = new Object(); 2 @Override 3 public void run() { 4 while (true){ 5 //使用同步代码块,obj被称为锁对象 6 synchronized (obj){ 7 if(num > 0){ 8 //sleep(5),让当前线程休眠5毫秒,此时CPU会执行其他线程 9 try { Thread.sleep(5); } catch (InterruptedException e) {}10 num--;11 System.out.println(Thread.currentThread().getName() + "***********" + num);12 }else{13 break;14 }15 }16 }17 }结果:
原因:
- 当线程执行到 synchronized (obj) 时,会获取尝试 obj 对象
- synchronized 保证多线程下,只有一个线程能获取到obj对象,其他线程就会阻塞(暂停)
- 多个线程同时执行到 synchronized (obj) 这句代码时,只有一个线程能够拿到obj,其他线程暂停
- 当持有 obj 的线程从 synchronized 退出后,会释放 obj 对象,然后其他线程再次争夺 obj
这样就保证了 synchronized 中的代码在某一时刻只能被一个线程执行
- 好处和弊端
好处:解决多线程数据安全问题
弊端:同步代码块同时只能被一个线程执行,降低效率
synchronized注意事项
- 必须是多个线程
- 线程数>1,就是多线程
- 多线程争抢的锁对象只能有一个,下图中的做法要坚决抵制(会被开哦)
2.同步方法
把 synchronized 放到方法上,那么这个方法就是同步方法
- 演示
1 class Hero implements Runnable{ 2 public static int num = 10; 3 public static Object obj = new Object(); 4 @Override 5 public void run() { 6 //run方法中调用同步方法 7 this.show(); 8 } 9 //同步方法10 public synchronized void show(){11 while (true){12 if(num > 0){13 try { Thread.sleep(5); } catch (InterruptedException e) {}14 num--;15 System.out.println(Thread.currentThread().getName() + "***********" + num);16 }else{17 break;18 }19 }20 }21 22 }23 public class ThreadDemo {24 public static void main(String[] args) {25 Hero hero = new Hero();26 Thread yase = new Thread(hero, "yase");27 Thread daji = new Thread(hero, "daji");28 yase.start();29 daji.start();30 }31 }结果:
2.同步方法的锁对象
使用同步代码块时,需要一个锁对象,但是同步方法并不需要,因为:同步方法的锁是this(当前对象)
证明:
1 class Hero implements Runnable{ 2 public static Object obj = new Object(); 3 @Override 4 public void run(){ 5 //1. 使用 this 作为锁对象 6 synchronized (this){ 7 try { Thread.sleep(2000); } catch (InterruptedException e) {} 8 System.out.println(Thread.currentThread().getName() + "。。。。。。。"+this); 9 }10 }11 //2. 同步方法的锁是 this12 public synchronized void show(){13 System.out.println(Thread.currentThread().getName() + "。。。。。。。"+this);14 }15 16 }17 public class ThreadDemo {18 public static void main(String[] args) throws Exception {19 Hero hero = new Hero();20 //3. 启动 ‘亚瑟’ 线程,自动执行run方法21 new Thread(hero,"亚瑟").start();22 23 //4. 主线程休眠100毫秒后,执行show这个同步方法24 Thread.sleep(100);25 hero.show();26 }27 }上面,run方法中的 this 就是 hero对象,结果:
原因:
- 亚瑟线程先执行,走到 Thread.sleep(2000) 时,休眠2秒,但这时它已经持有 this (也就是hero对象)
- main线程休眠100毫秒后,执行show方法尝试获取this,无法获取到,于是等待1.9秒
- 所以最终结果。。。。。
3.静态同步方法
synchronized 放到静态方法上,就是静态同步方法,锁对象是:Hero.class
- Hero.class 也是对象,称为Class对象或字节码对象,是jvm把Hero.class加载到内存后创建一个对象
修改代码:
1 class Hero implements Runnable{ 2 public static Object obj = new Object(); 3 @Override 4 public void run(){ 5 //1. 锁对象换成了Hero.class,也就是Hero字节码对象 6 synchronized (Hero.class){ 7 try { Thread.sleep(2000); } catch (InterruptedException e) {} 8 System.out.println(Thread.currentThread().getName() + "。。。。。。。"+Hero.class); 9 }10 }11 //2. show方法改成static12 public static synchronized void show(){13 System.out.println(Thread.currentThread().getName() + "。。。。。。。"+Hero.class);14 }15 16 }17 public class ThreadDemo {18 public static void main(String[] args) throws Exception {19 Hero hero = new Hero();20 new Thread(hero,"亚瑟").start();21 22 Thread.sleep(100);23 Hero.show();24 }25 }结果:
4.死锁
A 线程等待 B 线程释放锁,同时 B 线程也在等到 A 线程释放锁,比如:
通常发生在同步嵌套
- 同步嵌套:synchronized 中 还有 synchronized
示例:
1 class Hero implements Runnable{ 2 //1. 搞两个锁对象 A、B 3 public static Object A = new Object(); 4 public static Object B = new Object(); 5 6 public boolean bool; 7 Hero(boolean bool){ 8 this.bool = bool; 9 }10 @Override11 public void run(){12 //2. 当 bool 是 true 13 if(bool){14 synchronized (A){//先获取 A 锁,然后睡一会儿,再获取 B 锁15 System.out.println(Thread.currentThread().getName() + "------拿到A锁,准备获取B锁。。。。。。。");16 try { Thread.sleep(200); } catch (InterruptedException e) {}17 synchronized (B){18 System.out.println(Thread.currentThread().getName() + "。。。。。。。");19 }20 }21 }else{//3. 当 bool 是 false 22 synchronized (B){ //先获取B锁,睡一会儿,再获取A锁23 System.out.println(Thread.currentThread().getName() + "------拿到B锁,准备获取A锁。。。。。。。");24 try { Thread.sleep(200); } catch (InterruptedException e) {}25 synchronized (A){26 System.out.println(Thread.currentThread().getName() + "。。。。。。。");27 }28 }29 }30 }31 }32 public class ThreadDemo {33 public static void main(String[] args) throws Exception {34 new Thread(new Hero(true), "yase").start();35 new Thread(new Hero(false), "daji").start();36 }37 }结果:
上图中,死锁导致程序无法继续运行,但同时也一直不结束
执行过程分析:
- 假设 yase 线程先执行,在获取的A锁后,睡觉
- daji线程接着执行,在获取B锁后,睡觉
- yase线程睡醒后,尝试获取B锁,但是已经被daji线程拿到了,于是阻塞
- daji线程睡醒后,尝试获取A锁,但是已经被yase线程拿到了,也阻塞
- yase线程需要获取B锁后,执行完代码,再能释放A锁
- daji线程需要获取A锁后,执行完代码,再能释放B锁
- 最终两个线程都阻塞,程序卡死
总结:死锁是开发中的禁忌,绝对禁止出现,所以开发中尽量避免同步代码块嵌套使用
7.等待唤醒机制
1.体验
Object 类中有 wait()、notify() 这两个方法
- wait():让当前线程进入等待状态
- 使用方式:锁对象.wait(),必须放到 同步代码块 或 同步方法 中
- notify():唤醒对应的正在wait()的线程
- 使用方式:锁对象.notify(),必须放到 同步代码块 或 同步方法 中
- 调用 wait() 和 notify() 的锁对象必须是同一个
作用:使用这两个方法可以让多个线程间产生通信
示例:
1 class Hero implements Runnable{ 2 //1. 搞一个锁对象 3 public static Object lock = new Object(); 4 @Override 5 public void run(){ 6 synchronized (lock){ 7 System.out.println(Thread.currentThread().getName() + "。。。。。。。获取锁,然后进入等待状态"); 8 try { 9 //2. 当前线程等待,使用方式:锁对象.wait(),而且必须放到同步代码块或者同步方法中10 lock.wait();11 } catch (InterruptedException e) { }12 System.out.println(Thread.currentThread().getName() + "。。。。。。。结束");13 }14 }15 }16 public class ThreadDemo {17 public static void main(String[] args) throws Exception {18 //3. 创建一个 yase 线程19 Thread yase = new Thread(new Hero(),"yase");20 yase.start();21 22 //4. 主线程休眠2秒23 Thread.sleep(2000);24 25 //5. 使用notify唤醒yase线程,让他继续执行26 synchronized (Hero.lock){27 System.out.println("主线程唤醒yase。。。。。。。");28 //使用方式:锁对象.notify(),唤醒对应的正在等待状态的线程,必须放到同步代码块或者同步方法中29 Hero.lock.notify();30 }31 }32 }结果:
2.注意:
1.wait、notify必须放到同步代码块或同步方法中
2.必须是:锁对象.wait(),如果锁对象是this,那么:this.wait()
3.必须是:锁对象.notify()
4.调用 wait() 和 notify() 的锁对象必须是同一个
如果锁对象不一样,程序则不会结束原因:a. Hero.class.notify();只能唤醒锁对象是Hero.class 且执行了Hero.class.wait()的线程 b. 而yase线程的锁对象是lock,执行了lock.wait(),一直在等待被人唤醒,所以程序一直不结束5.执行 notify 后,正在 wait 的线程并不是立即运行,需要等待执行 notify 的线程释放锁6.wait()等待时候,会释放锁对象上图可以看出,yase先执行,如果wait()不释放锁,那么就无法执行Hero.lock.notify()这句代码
7.如果有多个线程都在wait(),notify只会随机的唤醒一个
1 public static void main(String[] args) throws Exception { 2 //创建 2 个线程 3 Thread yase = new Thread(new Hero(),"yase"); 4 yase.start(); 5 Thread laoyase = new Thread(new Hero(),"laoyase"); 6 laoyase.start(); 7 Thread.sleep(2000); 8 9 //使用方式:锁对象.notify(),唤醒对应的正在等待状态的线程,必须放到同步代码块或者同步方法中10 synchronized (Hero.lock){11 System.out.println("主线程唤醒。。。。。。。");12 Hero.lock.notify();//有多个线程都在wait(),notify只会随机的唤醒一个13 }14 }结果:
上图,yase线程结束了,但是laoyase这个线程还在阻塞
7.notifyAll()
如果想全部唤醒,可以使用notifyAll(),或者执行多次notify(),比如:
结果:
执行多次notify()方法也可以,全部唤醒
8.特殊情况下的notify会唤醒所有1 class Hero extends Thread{ 2 public void run(){ 3 //1. 注意这里的锁对象是this 4 synchronized (this){ 5 //2. 执行 notify 唤醒所有 6 this.notify(); 7 System.out.println("lock线程运行----"+this.getName()); 8 } 9 }10 }11 public class ThreadDemo {12 public static void main(String[] args) throws Exception {13 //3. 创建一个Thread对象,作为锁对象14 Hero yase = new Hero();15 yase.setName("yase");16 17 new Thread(new Runnable() {18 @Override19 public void run() {20 System.out.println("线程daji开始运行。。。。。。");21 //4. 注意,这里的锁对象是一个线程对象22 synchronized (yase){23 try {24 yase.wait();25 System.out.println("线程daji结束----"+yase.getName());//输出lock线程的名称26 } catch (InterruptedException e) {27 }28 }29 }30 }, "daji").start();31 32 new Thread(new Runnable() {33 @Override34 public void run() {35 System.out.println("线程lvbu开始运行。。。。。。");36 //4. 注意,这里的锁对象是一个线程对象37 synchronized (yase){38 try {39 yase.wait();40 System.out.println("线程vbul结束----"+yase.getName());41 } catch (InterruptedException e) {42 }43 }44 }45 }, "lvbu").start();46 47 Thread.sleep(2000);48 //5. 启动 yase 线程49 yase.start();50 }51 }结果:
代码分析:
- 首先创建一个yase线程对象
- 然后创建了 daji、lvbu 两个线程,这两个线程都把 yase 对象作为锁对象
- 这两个线程分别启动后都执行 wait 方法,进入等待模式
- 最后yase线程启动,执行run方法时,调用 yase.notify();
- 结果:daji、lvbu 都被唤醒
3.生产消费模式
wait()、notify() 经常用于生产消费模式,这是工作中最常见的设计方案:生产者生产数据,消费者消费数据
需求:生产者生产商品,如果货架上已经有商品就不再生产,消费者消费商品,如果货架上没有就通知生产者
代码:
1 class Goods{ 2 String name = "哈根达斯"; 3 4 //0:表示货架上没有商品,需要生产 5 //1:表示货架上已有商品,需要消费 6 int count = 0; 7 } 8 //消费者 9 class Consumer implements Runnable{10 private Goods goods;11 Consumer(Goods goods){12 this.goods = goods;13 }14 @Override15 public void run(){16 while (true){17 synchronized (goods){18 if(goods.count == 1){19 System.out.println(Thread.currentThread().getName()+"。。。。。。消费商品---"+goods.count);20 goods.count = 0;//消费商品21 goods.notify();//唤醒消费者22 //让自己自己等待23 try {goods.wait();} catch (InterruptedException e) {}24 }25 }26 }27 }28 }29 //生产者30 class Producer implements Runnable{31 32 private Goods goods;33 Producer(Goods goods){34 this.goods = goods;35 }36 @Override37 public void run(){38 while (true){39 synchronized (goods){40 if(goods.count == 0){41 System.out.println(Thread.currentThread().getName()+"。。。。。。生产商品---------"+goods.count);42 goods.count = 1;//生产商品,设置count=143 goods.notify();//唤醒消费者44 //自己等待45 try {goods.wait();} catch (InterruptedException e) {}46 }47 }48 }49 }50 }51 public class ThreadDemo {52 public static void main(String[] args) throws Exception {53 Goods goods = new Goods();54 new Thread(new Producer(goods), "伊利").start();55 new Thread(new Consumer(goods), "亚瑟").start();56 }57 }结果:
两个线程互相唤醒,交替执行4.wait和sleep
相同点:
- 都可以暂时停止一个线程
不同点:
- sleep必须指定睡眠时间,wait可以指定也可以不指定
- sleep是 Thread 的静态方法,wait是 Object 类的成员方法
- sleep 不释放锁,wait 释放锁
- sleep 等待一定时间后自定运行,wait需要 notify 或 notifyAll 唤醒
- wait 必须配合synchronized使用,sleep不必
- sleep 会让线程进入 TIMED_WAITING 状态,wait让线程进入 WAITING 状态
- 之后会详细解释线程状态
8.停止线程
Thread类中有一个 stop 方法,可以停止线程,但是已经过时,不推荐使用
其实,停止线程的最好方式是让线程正常结束
具体方式:
- 声明一个变量,设置一个开关
1 class Hero extends Thread{ 2 //1. 定义一个boolean值,控制线程是否结束 3 boolean bool; 4 public void run(){ 5 for (int i = 0; i < 10; i++) { 6 if(bool){ 7 //2. 当bool=true时,跳出循环,run方法结束,线程也就停止了 8 break; 9 }10 System.out.println(Thread.currentThread().getName()+"。。。。。。"+i);11 try {Thread.sleep(500);} catch (InterruptedException e) {}12 }13 System.out.println(Thread.currentThread().getName()+"线程结束。。。。。。");14 }15 }16 public class ThreadDemo {17 public static void main(String[] args) throws Exception {18 Hero hero = new Hero();19 hero.start();20 21 Thread.sleep(3000);22 System.out.println("main线程休息2秒后,设置bool=true。。。。。。");23 hero.bool = true;24 }25 }结果:
- interrupt 方法Thread 中有个 interrupt 方法,可以用来终止线程
- 注意:interrupt 并不会终止线程,它只是将线程的中断标记设为true
- 使用 isInterrupted() 方法判断线程的终端标记是否为 true
1 class Hero extends Thread{ 2 public void run(){ 3 for (int i = 0; i < 100000000; i++) { 4 //如果当前线程的中断标记是true,跳出循环,线程结束 5 if(Thread.currentThread().isInterrupted()){ 6 System.out.println(Thread.currentThread().getName()+"被打断,跳出循环。。。。。。"); 7 break; 8 } 9 System.out.println(Thread.currentThread().getName()+"。。。。。。"+i);10 }11 System.out.println(Thread.currentThread().getName()+"线程结束。。。。。。");12 }13 }14 public class ThreadDemo {15 public static void main(String[] args) throws Exception {16 Hero hero = new Hero();17 hero.start();18 19 Thread.sleep(1000);20 System.out.println("main线程休息1秒后,执行interrupt。。。。。。");21 hero.interrupt();22 }23 }结果:
- interrupt 和 sleep
上面代码中我们把sleep语句给去掉了,这时因为,如果线程在 sleep、wait 时被 interrupt
会抛出 InterruptedException,比如:
结果:
9.守护线程
目前我们创建的线程是前台线程,也叫一般线程,线程中还有一种比较特殊的:守护线程
- 通过 setDaemon 方法可以把一个线程设置为守护线程
守护线程跟一般线程差不多,只是结束的时有些区别
- 当前台线程结束,守护线程会自动结束
示例:
1 public static void main(String[] args) throws Exception { 2 //1. 创建一个线程 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 for (int i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName()+"。。。。。。"+i); 8 try { 9 Thread.sleep(500);10 } catch (InterruptedException e) {11 }12 }13 System.out.println("yase线程结束。。。。。。。");14 }15 },"yase");16 //2. 设置yase是守护线程17 yase.setDaemon(true);18 yase.start();19 20 //3. main是一般线程,休眠3秒后结束21 Thread.sleep(3000);22 System.out.println("main线程休息3秒后,over。。。。。。");23 }结果:
main线程结束后,守护线程自动结束
另外,当所有前台线程结束后,守护线程才会结束
示例:
1 public static void main(String[] args) throws Exception { 2 //1. 创建一个线程 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 for (int i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName()+"。。。。。。"+i); 8 try { 9 Thread.sleep(500);10 } catch (InterruptedException e) {11 }12 }13 System.out.println("yase线程结束。。。。。。。");14 }15 },"yase");16 //2. 设置yase是守护线程17 yase.setDaemon(true);18 yase.start();19 20 //3. daji是一般线程21 Thread daji = new Thread(new Runnable() {22 @Override23 public void run() {24 for (int i = 0; i < 20; i++) {25 System.out.println(Thread.currentThread().getName()+"-----------------------"+i);26 try {27 Thread.sleep(500);28 } catch (InterruptedException e) {29 }30 }31 System.out.println("daji线程结束。。。。。。。");32 }33 },"daji");34 daji.start();35 36 37 //4. main是一般线程,休眠3秒后结束38 Thread.sleep(3000);39 System.out.println("main线程休息3秒后,over。。。。。。");40 }结果:
上图,main线程执行最后一句代码后,yase 和 daji 线程依旧在运行
当 daji线程结束后,yase这个守护线程也随之结束
10.线程状态
Thread类中有 State 枚举类,罗列了线程状态
1.初始(NEW):新创建了一个线程对象,但还没有调用start()方法
2.运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”
- 线程调用了start()方法后,这时线程位于可运行线程池中,等待获取CPU的使用权,此时处于就绪状态(ready)
- 就绪状态的线程在获得CPU时间片后变为运行中状态(running)
3.阻塞(BLOCKED):表示线程等待获取锁
- 遇到synchronized变为阻塞状态
4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(唤醒或打断)
- 遇到 wait、join 变为等待状态
5.超时等待(TIMED_WAITING):跟WAITING不同,可在指定时间后自己运行
- 遇到wait(毫秒数)变为超时等待
- 如果在等待时被唤醒,立即执行
- 等待超时后,先尝试获取锁,不能获取则阻塞,获取到就运行
6. 终止(TERMINATED):表示该线程已经执行完毕
状态切换时常用方法
start()
启动一个线程
run()
线程需要执行的代码,run方法结束,该线程结束
sleep(long millis)
线程休眠,但不释放锁
join()
等待该线程结束,可以让线程顺序执行
wait()/notify()/notifyAll()
wait()使当前线程等待,前提是 必须先获得锁,一般配合synchronized 关键字使用只有当 notify/notifyAll() 被执行时候,才会唤醒该线程继续执行,直到执行完synchronized 代码块或是再次遇到wait() notify/notifyAll() 的执行只是唤醒等待的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁
4.设计模式
1.概述
设计模式:就是解决问题的方案,是前辈们对解决某些问题的一些经验总结
好处:提高代码重用性、健壮性,让代码更容易被他人理解
目前 java 中比较流行的有23种设计模式
2.单例模式
场景:多个程序需要操作同一个对象,程序A 修改后,程序B 需要拿到对象中的最新数据继续操作
如何实现?
- 类的外部不能通过 new 关键字创建对象,所以构造方法应该是 private
- 在这个类中自己 new 一个对象,并且提供 get 方法,让外部可以获取
示例:饿汉模式
1 class Single{ 2 3 //1. 私有的构造方法,外部不能使用,但是本类中可以使用 4 private Single(){ 5 } 6 //2. 搞一个静态变量,这样类初始化时候,instance就已经有值了 7 //设置为private,是避免外部修改这个变量,当然也可以使用final 8 private static Single instance = new Single(); 9 10 //3. 对外提供获取对象的方式,这样每次调用这个方法拿到的都是同一个对象11 public static Single getInstance(){12 return instance;13 }14 }懒汉模式
1 class Single{ 2 3 //1. 私有的构造方法,外部不能使用,但是本类中可以使用 4 private Single(){ 5 } 6 //2. 搞一个静态变量 7 private static Single instance = null; 8 9 //3. 对外提供获取对象的方式10 public static Single getInstance(){11 //如果 instance 是空的时候,在创建对象,这就是懒汉式12 if(instance == null){13 synchronized (Single.class){14 if(instance == null){15 instance = new Single();16 }17 }18 }19 return instance;20 }21 //方法中用了两次if判断,这就是经典的双检锁22 }11.多个线程顺序执行(了解)
某些情况下虽然开启了多个线程,但是还是希望他们能个一个接一个的执行,这就是顺序执行
Thread中有一个 join 方法,使用它可以让线程顺序执行
1 public static void main(String[] args) throws Exception { 2 //1. 多个线程之间顺序执行 3 Thread t1 = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("线程1执行。。。。。。"); 7 } 8 }); 9 Thread t2 = new Thread(new Runnable() {10 @Override11 public void run() {12 System.out.println("线程2执行。。。。。。");13 }14 });15 Thread t3 = new Thread(new Runnable() {16 @Override17 public void run() {18 System.out.println("线程3执行。。。。。。");19 }20 });21 22 t1.start();23 t1.join();//执行join方法后,main线程等待t1结束后,才继续往下执行24 t2.start();25 t2.join();26 t3.start();27 t3.join();28 }结果:
上面的代码一定要注意:t1.join()这句代码是由main线程执行的,所以main线程会等待
12.线程优先级(了解)
线程有优先级,优先级高的获取CPU执行权的概率就大
- 通过getPriority、setPriority 获取和设置线程的优先级,最大是10,最小是1
- 如果不设置,默认的优先级是5
1 class Hero extends Thread{ 2 @Override 3 public void run() { 4 for (int i = 0; i < 1000000; i++) { 5 System.out.println(Thread.currentThread().getName()+"。。。。。。"+i); 6 } 7 } 8 } 9 public class ThreadDemo {10 public static void main(String[] args) throws Exception {11 Thread yase = new Thread(new Hero(),"yase");12 yase.setPriority(10);//设置优先级,10最大,1最小13 yase.start();14 15 Thread daji = new Thread(new Hero(), "daji");16 daji.setPriority(1);//设置优先级,10最大,1最小17 daji.start();18 19 //主线程多睡一会儿20 Thread.sleep(60000);21 }22 }结果:
yase线程执行到了54万,daji线程才执行到39万,明显yase线程有更高的CPU执行权
环球头条:多线程全面总结
1 单线程单线程:只有一个线程,即CPU只执行一个任务(一个线程)1classHero{2Stringname;3Hero(Stringname){4this name=n
来源: 环球头条:多线程全面总结
怎么注册微信公众号?
Pro版同款!荣耀Magic5至臻版影像泄露:5000万像素旗舰三摄
今日要闻!《LOL》英雄价格正式调整!全面下调
全球热议:理想L9车主实惨:白天打开星环模式灯 扣1分罚款100元?
iPhone良品率不足50%也不怕 富士康又在印度设立新工厂
小鹏汽车欲靠P7“回血”:老款清库再降3.5万、新车下周上市
降价后真香了!特斯拉中国2月销量出炉:暴涨130%
9.98万起杀疯!比亚迪王朝系列2月销量超10万台:秦PLUS贡献3成
2月新能源汽车销量榜:比亚迪一家占比近4成 第三名暴走
世界资讯:委员蒋胜男谈35岁职场危机:根源是“996” 必须改变
今日讯!中国性能车!全新领克03 TCR赛车官图发布:售价超百万
排查系统执行SQL与数据库直接执行结果不一致的问题
每日资讯:详细剖析|袋鼠云数栈前端框架Antd 3.x 升级 4.x 的踩坑之路
记录--手摸手带你撸一个拖拽效果
天天讯息:VSCode官方的配置同步方案
快报:144MB缓存立大功 AMD锐龙7000X3D内存自由:4800都稳赢i9
环球观点:AMD RX 7900 XT价格全面雪崩:沦落到RTX 4070 Ti的级别
《旷野之息》发售6周年:续作《塞尔达传说:王国之泪》发布新预告
当前视点!中国围棋第一人易主!李轩豪超越柯洁 AI立大功
环球快讯:vivo推出“手语翻译官”应用:准确率可达80%以上
网络通信——TCP “三次握手“、“四次挥手“ 详解
PHP语言在线代码运行编译工具推荐
今日报丨JavaScript 回调函数属于闭包?
全球快看:ChatGPT开放API,上来就干到最低价,可以人手一个ChatGPT了
全球新动态:Spring事务使用注意事项
【全球独家】限制儿童支付金额方便了!微信青少年模式升级:一键开启上线
三排七座!仰望U8内饰曝光:比亚迪首款百万豪车来了
世界热门:刚失败一次后 日本不放弃:新一代运载火箭尝试再度发射
热到离谱?首个冲上20℃的北方省会诞生 下周或破30℃
欧洲2035年禁售燃油车要黄?德国公然反对!意大利:我也不同意
环球精选!登月用!中国新一代载人火箭预计2027年首飞
天天即时:《狂飙》“大嫂”高叶上手小米13:女神持机美如画
全球观速讯丨火山引擎 DataTester:A/B 实验如何实现人群智能化定向?
环球快资讯丨Redis分布式锁常见坑点分析
世界今日讯!eas里客户端保存,提交里增加校验规则和必填
访问者模式
世界微动态丨网友偶遇眼镜王蛇求助 博物杂志:务必远离、打输住院打赢坐牢
世界今亮点!Vtuber因直播《霍格沃茨之遗》被骚扰 宣布毕业
天天讯息:委员建议研究生招生规模动态扩大:缓解考研难
全球聚焦:收个滴滴Offer:从小伙三面经历,看看需要学点啥?
环球热资讯!Study for Go! Chapter one - Type
环球最新:手写模拟Spring底层原理-Bean的创建与获取
速看:兰博基尼领衔 今年值得期待的7款跑车 买不起还不能看看?
女子试用期被辞退 现场给HR普法:金句频出网友点赞称解气
每日聚焦:靠ChatGPT年入百万!合法还不限学历专业:一般人我不告诉他(doge)
全球新消息丨韩系车日子不好过!起亚狮铂拓界限时优惠:降3万还给大礼包
zip文件结构
头条:与时俱进推动智慧城市建设,智慧管网监测加强城市治理能力
全球视讯!Java项目集成工作流activiti,会签
简单介绍Python中如何给字典设置默认值
播报:LG:三星QD OLED电视更容易烧屏
世界聚焦:掏耳朵怎么就这么爽!
今日视点:不只全面屏!努比亚Z50 Ultra后摄惊艳:黄金镜皇组合
男子月薪3千相亲角“反向相亲”气到大妈 大爷理解:靠颜值吃饭
每日热文:吴京+杰森斯坦森主演!《巨齿鲨2》暑期上映 国内有望同步
环球视点!ffmpeg视频上传及压缩Linux配置篇下
世界快资讯:【Avalonia】【跨平台】关于Prism项目模块化在Linux下路径问题
浙大揭秘吃鱼为什么会变聪明 网友:告诉老默 我想吃鱼了
上海消保委提醒谨慎购买威马汽车:经营异常、消极应对投诉
特斯拉将放弃稀土材料 中国公司无惧:目前没有东西替代
即时:B站两款自研游戏将上线 CEO陈睿:能挣钱的游戏只剩下两种
《生化危机4:重制版》新演示/截图 里昂拯救黑丝碍事梨
焦点日报:配置资源管理Secret和ConfigMap
环球视点!Windows故障转移群集 和 SQLServer AlwaysOn 搭建教程
(数据库系统概论|王珊)第九章关系查询处理和关系优化-第一节:查询处理
全球速递!视频上传及压缩SpringBoot篇上
世界热门:el-input 使用 回车键会刷新页面的问题
全球最强!传音260W快充手机将亮相:10分钟内充满
性能对标奔驰大G 比亚迪“F品牌”首车曝光:够硬够强
世界快消息!传欧盟准备批准微软收购动视-暴雪
当前报道:女司机“神操作”:100来公里高速连撞4次 竟甩锅路太窄
世界视讯!又一大作优化翻车!《卧龙:苍天陨落》RTX 4090依旧闪退
12GB+256GB到手仅2699元!Redmi K60正式开启降价
温州特斯拉事故驾驶员家属发声:记不清车辆失控场景 妻子去世自责
环球时讯:中国航天员遇到外星人怎么办?载人航天总师:积极交流 星际合作
焦点要闻:漫威等好莱坞大片中国市场遇冷:大家不爱看了 不符合国人审美、文化观
550元 富士发布Instax Mini 12拍立得相机 支持APP存照片
环球即时:卷成白菜价!致态TiPlus 7100固态硬盘新史低:1TB仅549元
环球头条:马斯克10万亿美元“改造地球”背景下!特斯拉电机要完全不用稀土:专家回应有可能
读Java性能权威指南(第2版)笔记07_即时编译器上
今头条!天问二号任务已获得国家批准立项:要从小行星2016 HO3采样返回
实现 Vue 折叠面板组件
委员:996制度是导致就业难、生育率低的重大原因
当前播报:电商价格战开打!京东百亿补贴上线:全场包邮 买贵双倍赔
特斯拉未来要狂暴降价:就靠这改变世界?其实都被骗了!
世界快资讯:豆瓣8.9分!韩国拼体格真人秀在欧美爆红
暗黑三国风!《卧龙:苍天陨落》正式上线:298元 GTX 1650就能玩
3.3 数据结构 时间复杂度 和空间复杂度 计算
环球简讯:004. html篇之《标签分类和嵌套》
天天动态:星巴克国内最大对手!瑞幸咖啡财报:年收入首次突破百亿
硬件狗狗3.3新版发布:跑分排行 实时PK
【环球热闻】使用ansible部署服务到k8s
专家:双休制度很难被改变 可以试试“做四休三”
【当前独家】同样是PCIe 5.0 SSD:Intel、AMD跑分竟不一样!差距达30%
世界观焦点:大熊猫为什么近期扎堆回国?美日等国养不起“国宝”了吗?完全是误解
当前速讯:女子称因准点下班试用期第3天被辞退:还被领导一顿痛骂
全球聚焦:R数据分析:做量性研究的必备“家伙什”-furniture包介绍
003. html篇之《表单》
全球即时:Codeforces 1774 G Segment Covering 题解 (观察性质,倍增)