线程八大基础核心
在java多线程并发编程中,有八大基础核心。它们分别是:
1.创建线程的方式
2.线程启动
3.线程停止
4.线程生命周期
5.线程相关的方法
6.线程相关的属性
7.线程异常处理
8.线程安全
一、创建线程的方式:
在网络上,关于创建线程的方式。主流的说法有四种:继承Thread、实现Runnable、线程池、Callable与Future
在官方文档,关于创建线程的方式。有且仅有两种:继承Thread、实现Runnable
在java编程语言中,创建线程的方式,我们以官方文档为准,只有两种。关于说线程池、Callable与Future是项目开发中常用的方式,它们的底层其实还是Runnable。
1.1 继承Thread
package com.anan.thread.createthread; public class ThreadDemo { public static void main(String[] args) { // 创建线程对象,并启动 Thread t1 = new MyThread(); t1.start(); } } /** * 继承Thread方式,创建线程 */ class MyThread extends Thread{ @Override public void run() { System.out.println("继承Thread方式,创建线程"); } }
1.2 实现Runnable接口
package com.anan.thread.createthread; public class RunnableDemo { public static void main(String[] args) { // 创建线程对象,并启动 Runnable r1 = new MyRunnable(); Thread t1 = new Thread(r1); t1.start(); } } /** * 实现Runnable接口,创建线程 */ class MyRunnable implements Runnable{ public void run() { System.out.println("实现Runnable接口,创建线程"); } }
在实际开发中,推荐使用实现Runnable接口方式,原因有:
- 实现Runnable接口方式,可以将线程处理业务,与创建线程分离,从而解耦
- 实现Runnable接口方式,扩展性更好,因为在java编程语言中,只能继承一个父类,而可以实现多个接口
- 实现Runnable接口方式,配合线程池使用,有更好的资源利用率和性能
二、启动线程
2.1 在java编程语言中,创建好线程对象后,通过调用start方法,启动线程。
在start方法内部启动流程是:
2.1.1.判断当前线程状态,是否是NEW状态
2.1.2.如果当前线程是NEW状态,将当前线程加入线程组
2.1.3.调用本地方法start0(),开启线程
2.2 这里有两个问题:
1.问题一:可以调用两次start方法吗?
不能,因为线程只有在NEW状态下,可以启动
2.问题二:可以调用run方法,启动线程吗?
不能,因为直接调用run方法,相当于在主线程main下调用普通方法,并没有开启新的线程
三、停止线程
在java编程语言中,线程停止指的是线程的生命周期结束,分为正常执行结束和意外结束。
3.1 正常停止
启动线程,调用run方法执行,当run方法正常执行结束,则线程正常停止。
package com.anan.thread.stopthread; /** * 启动线程,调用run方法执行,当run方法正常执行结束,则线程正常停止 */ public class NormalStopThreadDemo { public static void main(String[] args) { // 创建线程对象 Runnable r1 = new MyRunnable(); Thread t1 = new Thread(r1); // 启动线程 t1.start(); } } /** * 实现Runnable接口,创建线程 */ class MyRunnable implements Runnable{ public void run() { System.out.println("线程准备开始执行......"); System.out.println("......线程执行中......"); System.out.println("线程执行结束......"); } }
3.2 意外停止
启动线程,调用run方法执行,当run方法发生异常后,则线程意外停止。
1 package com.anan.thread.stopthread; 2 3 /** 4 * 启动线程,调用run方法执行,当run方法发生异常后,则线程意外停止 5 */ 6 public class AccidentStopThreadDemo { 7 8 public static void main(String[] args) { 9 // 创建线程对象 10 Runnable r1 = new MyRunnable2(); 11 Thread t1 = new Thread(r1); 12 13 // 启动线程 14 t1.start(); 15 16 } 17 } 18 19 /** 20 * 实现Runnable接口,创建线程 21 */ 22 class MyRunnable2 implements Runnable{ 23 24 public void run() { 25 System.out.println("线程准备开始执行......"); 26 System.out.println("......线程执行中......"); 27 // 发生意外 28 int i = 1/0; 29 30 System.out.println("线程执行结束......"); 31 } 32 }
3.3 中断停止
3.3.1 中断信号停止
启动线程,调用run方法执行,通过线程对象调用interrupt中断方法,发送中断信号停止线程。
1 package com.anan.thread.stopthread; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 启动线程,调用run方法执行,通过线程对象调用interrupt中断方法,发送中断信号停止线程 7 */ 8 public class InterruptSignalStopThread { 9 10 public static void main(String[] args) throws InterruptedException { 11 // 创建线程对象 12 Runnable r1 = new MyRunnable3(); 13 Thread t1 = new Thread(r1); 14 15 // 启动线程 16 t1.start(); 17 // 主线程休眠1毫秒后,向t1线程发送中断信号 18 TimeUnit.MILLISECONDS.sleep(1); 19 t1.interrupt(); 20 } 21 } 22 23 /** 24 * 实现Runnable接口,创建线程 25 */ 26 class MyRunnable3 implements Runnable{ 27 28 public void run() { 29 System.out.println("线程准备开始执行......"); 30 // 循环执行任务,直到收到中断信号为止 31 // isInterrupted()方法,返回线程是否被中断 32 int i = 0; 33 while ( ! Thread.currentThread().isInterrupted()){ 34 i++; 35 System.out.println("......线程第【" + i +"】次执行中......"); 36 } 37 System.out.println("收到中断信号,线程在第【" + i + "】次执行结束......"); 38 } 39 }
3.3.2 响应中断停止
启动线程,调用run方法执行,在run方法中有可响应中断的操作,比如:sleep、wait等。通过线程对象调用interrupt中断方法,发送中断信号响应中断停止。
1 package com.anan.thread.stopthread; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 启动线程,调用run方法执行,在run方法中有可响应中断的操作, 7 * 比如:sleep、wait等。通过线程对象调用interrupt中断方法,发送中断信号响应中断停止 8 */ 9 public class ResponseInterruptSignalStopThread { 10 11 public static void main(String[] args) throws InterruptedException { 12 // 创建线程对象 13 Runnable r1 = new MyRunnable4(); 14 Thread t1 = new Thread(r1); 15 16 // 启动线程 17 t1.start(); 18 // 主线程休眠1毫秒后,向t1线程发送中断信号 19 TimeUnit.MILLISECONDS.sleep(1); 20 t1.interrupt(); 21 } 22 } 23 24 /** 25 * 实现Runnable接口,创建线程 26 */ 27 class MyRunnable4 implements Runnable{ 28 29 public void run() { 30 System.out.println("线程准备开始执行......"); 31 // 循环执行任务,直到收到中断信号,通过sleep方法响应中断 32 int i = 0; 33 try{ 34 35 while ( i <= 100000000){ 36 i++; 37 System.out.println("......线程第【" + i +"】次执行中......"); 38 39 // 休眠10毫秒 40 TimeUnit.MILLISECONDS.sleep(10); 41 } 42 }catch (InterruptedException e){ 43 System.out.println("收到中断信号,sleep方法响应中断,线程在第【" + i + "】次执行结束......"); 44 e.printStackTrace(); 45 } 46 47 } 48 }
1.在java编程语言中,线程停止的情况有哪些?
1.1.在java编程语言中,线程停止的情况有:正常停止、意外停止、中断停止
1.2.在实际项目开发中,我们追求正常停止、中断停止。避免意外停止
2.在java编程语言中,如何人为优雅的停止线程?
2.1.在java编程语言中,通过调用线程对象的interrupt()方法,发送中断信号的方式,人为优雅的停止线程
2.2.所谓人为优雅,即指与线程协商的方式停止线程,而不是强制停止线程,最终的停止权交由线程本身来控制
3.在java编程语言中,为什么说坚决不要调用stop方法停止线程?
3.1.因为如果使用stop方法停止线程,它是一种暴力手段,即强制停止线程,有可能会导致线程任务执行的不完整,不安全。且在较新版本的jdk中,已经设置为过期的方法,不再推荐使用。
四、线程生命周期
状态转换案例
4.1 状态 NEW/RUNNABLE/TERMINATED
通过该案例,演示线程的NEW/RUNNABLE/TERMINATED状态。
1 package com.anan.thread.threadstate; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 演示线程状态:NEW/RUNNABLE/TERMINATED 7 */ 8 public class ThreadStateDemo1 { 9 10 public static void main(String[] args) throws InterruptedException{ 11 // 创建线程对象 12 String tName = "my-thread"; 13 Runnable r1 = new MyRunnable(); 14 Thread t1 = new Thread(r1,tName); 15 16 // 输出线程状态:NEW 17 System.out.println("1.新建线程:" + tName + "当前状态:" + t1.getState()); 18 19 // 启动线程,等待1毫秒,输出线程状态:RUNNABLE 20 t1.start(); 21 TimeUnit.MILLISECONDS.sleep(1); 22 System.out.println("2.启动线程后:" + tName + "当前状态:" + t1.getState()); 23 24 // 发送中断信号,等待1毫秒,输出线程状态:TERMINATED 25 t1.interrupt(); 26 TimeUnit.MILLISECONDS.sleep(1); 27 System.out.println("3.给线程发送中断信号后:" + tName + "当前状态:" + t1.getState()); 28 29 } 30 } 31 32 /** 33 * 实现Runnable接口,创建线程 34 */ 35 class MyRunnable implements Runnable{ 36 public void run() { 37 while (!Thread.currentThread().isInterrupted()){ 38 ;// 不断循环,等待中断信号发生,然后结束线程运行 39 } 40 System.out.println("中断信号发生," + Thread.currentThread().getName() + "准备结束运行."); 41 } 42 }
4.2 状态:BLOCKED
通过该案例,演示线程的BLOCKED状态。业务描述:
1.模拟获取共享资源银行:Bank账户余额信息,在获取账户余额时,需要加上同步锁
2.创建两个线程,并发获取银行账户余额,模拟当一个线程加锁操作中,另外一个线程只能阻塞等待
3.在主线程中,输出两个线程的状态
1 package com.anan.thread.threadstate; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 演示线程状态:BLOCKED 7 */ 8 public class ThreadStateBlockedDemo { 9 // 公共锁对象 10 public static final Object LOCK = new Object(); 11 12 /** 13 * 2.创建两个线程,并发获取银行账户余额, 14 * 模拟当一个线程加锁操作中,另外一个线程只能阻塞等待 15 */ 16 public static void main(String[] args) { 17 18 // 创建Runnable对象 19 Runnable r1 = new MyRunnable1(); 20 21 // 创建两个线程对象 22 String tName_1 = "my-thread-1"; 23 Thread t1 = new Thread(r1,tName_1); 24 25 String tName_2 = "my-thread-2"; 26 Thread t2 = new Thread(r1,tName_2); 27 28 // 启动线程t1,t2 29 t1.start(); 30 t2.start(); 31 32 // 输出两个线程:t1,t2当前状态 33 System.out.println("1.主线程"+ Thread.currentThread().getName() + 34 "打印,线程:" + t1.getName() + "当前状态:" + t1.getState()); 35 System.out.println("2.主线程"+ Thread.currentThread().getName() + 36 "打印,线程:" + t2.getName() + "当前状态:" + t2.getState()); 37 38 39 } 40 41 /** 42 * 1.模拟获取共享资源银行:Bank 43 * 账户余额信息,在获取账户余额时,需要加上同步锁 44 */ 45 public static void getBankMoney() { 46 synchronized (LOCK){ 47 System.out.println(Thread.currentThread().getName() + "线程,获取到锁###."); 48 // 休眠1秒,模拟业务操作 49 try { 50 TimeUnit.SECONDS.sleep(1); 51 // 打印输出账户余额 52 System.out.println("线程:" + Thread.currentThread().getName() + 53 "获取到账户余额了......"); 54 } catch (InterruptedException e) { 55 e.printStackTrace(); 56 } 57 } 58 59 System.out.println(Thread.currentThread().getName() + "线程,释放锁###."); 60 } 61 62 } 63 64 /** 65 * 实现Runnable接口,创建线程 66 */ 67 class MyRunnable1 implements Runnable{ 68 69 public void run() { 70 // 获取账户余额 71 ThreadStateBlockedDemo.getBankMoney(); 72 } 73 }
4.3 状态:WAITING
通过该案例,演示线程的WAITING状态。
1 package com.anan.thread.threadstate; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 演示线程状态:WAITING 7 */ 8 public class ThreadStateWaitingDemo { 9 10 // 创建公共锁 11 public final static Object LOCK = new Object(); 12 13 public static void main(String[] args) throws InterruptedException{ 14 // 创建线程对象 15 Runnable r1 = new MyRunnable3(); 16 String tName_1 = "my-thread-1"; 17 Thread t1 = new Thread(r1,tName_1); 18 19 // 启动线程,休眠1秒后,获取线程状态 20 t1.start(); 21 TimeUnit.SECONDS.sleep(1); 22 System.out.println("1.主线程"+ Thread.currentThread().getName() + 23 "打印,线程:" + t1.getName() + "当前状态:" + t1.getState()); 24 25 // 主线程唤醒t1线程,再次输出线程状态 26 synchronized (LOCK){ 27 LOCK.notify(); 28 } 29 TimeUnit.SECONDS.sleep(1); 30 System.out.println("2.主线程"+ Thread.currentThread().getName() + 31 "打印,线程:" + t1.getName() + "当前状态:" + t1.getState()); 32 } 33 } 34 35 /** 36 * 实现Runnable接口,创建线程 37 */ 38 class MyRunnable3 implements Runnable{ 39 40 public void run() { 41 System.out.println("线程:" + Thread.currentThread().getName() + 42 "即将进入等待:ThreadStateWaitingDemo.LOCK.wait(),等待主线程输出状态."); 43 synchronized (ThreadStateWaitingDemo.LOCK){ 44 try { 45 ThreadStateWaitingDemo.LOCK.wait(); 46 } catch (InterruptedException e) { 47 e.printStackTrace(); 48 } 49 } 50 System.out.println("线程:" + Thread.currentThread().getName() + 51 "被唤醒执行结束,等待主线程输出状态."); 52 } 53 }
4.4 状态:TIMED_WAITING
通过该案例,演示线程的TIMED_WAITING状态。
1 import java.util.concurrent.TimeUnit; 2 3 /** 4 * 演示线程状态:TIMED_WAITING 5 */ 6 public class ThreadStateTimedWaitingDemo { 7 8 public static void main(String[] args) throws InterruptedException{ 9 10 // 创建线程对象 11 Runnable r1 = new MyRunnable2(); 12 String tName_1 = "my-thread-1"; 13 Thread t1 = new Thread(r1,tName_1); 14 15 // 启动线程,休眠1秒后,获取线程状态 16 t1.start(); 17 TimeUnit.SECONDS.sleep(1); 18 System.out.println("1.主线程"+ Thread.currentThread().getName() + 19 "打印,线程:" + t1.getName() + "当前状态:" + t1.getState()); 20 21 } 22 } 23 24 /** 25 * 实现Runnable接口,创建线程 26 */ 27 class MyRunnable2 implements Runnable{ 28 29 public void run() { 30 System.out.println("线程:" + Thread.currentThread().getName() + 31 "准备休眠3秒:TimeUnit.SECONDS.sleep(3),等待主线程输出状态."); 32 try { 33 TimeUnit.SECONDS.sleep(3); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 }
1、线程生命周期中有哪些状态吗?
1.1.在java编程语言中,线程的生命周期总共有6种状态
2、各种状态对应的含义吗?
2.1.分别是:
新建:NEW
可运行:RUNNABLE
已终止:TERMINATED
阻塞:BLOCKED
等待:WAITING
计时等待:TIMED_WAITING
五、线程相关方法
1.在java编程语言中,与线程相关的方法主要有:
1.1.Object.wait/Object.notify/Object/notifyAll
1.2.Thread.sleep/Thread.join/Thread.yield
2、案例:wait/notify/notifyAll
wait:释放线程拥有的当前锁对象,让线程进入WAITING状态
notify:唤醒当前锁对象等待池(WaitSet)中,某一个线程
notifyAll:唤醒当前锁对象等待池(WaitSet)中,所有线程
业务描述:
1.通过wait与notify(notifyAll)实现线程协同,实现经典的生产者消费者模式
2.编写生产者,向队列中生产数据,如果队列满,生产者进行等待,并唤醒消费者进行消费
3.编写消费者,从队列中消费数据,如果队列空,消费者进行等待,并唤醒生产者进行生产
4.在主类main方法中,创建两个生产者线程进行生产
5.在主类main方法中,创建一个消费者线程进行消费
1 import java.util.ArrayList; 2 3 /** 4 * 生产者消费者模型:wait与notify(notifyAll) 5 */ 6 public class ProducerConsumerDemo { 7 8 // 定义公共资源:队列容量、队列 9 public final static Integer CAPACITY = 6; 10 public final static ArrayList<Integer> QUEUE = new ArrayList<Integer>(CAPACITY); 11 12 // 定义锁对象 13 public final static Object LOCK = new Object(); 14 15 public static void main(String[] args) { 16 17 // 创建两个生产者线程 18 Runnable r1 = new Producer(); 19 Thread producer0 = new Thread(r1,"producer-0"); 20 producer0.start(); 21 22 Thread producer1 = new Thread(r1,"producer-1"); 23 producer1.start(); 24 25 // 创建消费者线程 26 Runnable r2 = new Consumer(); 27 Thread consumer1 = new Thread(r2,"consumer-0"); 28 consumer1.start(); 29 30 } 31 } 32 33 /** 34 * 生产者 35 */ 36 class Producer implements Runnable{ 37 38 public void run() { 39 while(true){ 40 synchronized (ProducerConsumerDemo.LOCK){ 41 // 检查队列是否满,推荐用while而不是if 42 while(ProducerConsumerDemo.QUEUE.size() == 43 ProducerConsumerDemo.CAPACITY){ 44 // 如果队列满,则等待消费者消费 45 try { 46 System.out.println("队列满size:" +ProducerConsumerDemo.QUEUE.size()+ ",线程【" + 47 Thread.currentThread().getName() + "】正在等待消费者消费."); 48 ProducerConsumerDemo.LOCK.wait(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 } 53 54 // 如果队列不满,则正常生产 55 ProducerConsumerDemo.QUEUE.add(1); 56 System.out.println("线程【" + Thread.currentThread().getName() + 57 "】生产了数据.当前队列size:" + ProducerConsumerDemo.QUEUE.size()); 58 59 // 唤醒消费者 60 ProducerConsumerDemo.LOCK.notifyAll(); 61 } 62 } 63 } 64 } 65 66 /** 67 * 消费者 68 */ 69 class Consumer implements Runnable{ 70 71 public void run() { 72 while(true){ 73 synchronized (ProducerConsumerDemo.LOCK){ 74 // 检查队列是否空,推荐用while而不是if 75 while(ProducerConsumerDemo.QUEUE.isEmpty()){ 76 // 如果队列空,则等待生产者生产 77 try { 78 System.out.println("队列空size:" +ProducerConsumerDemo.QUEUE.size()+ ",线程【" + 79 Thread.currentThread().getName() + "】正在等待生产者生产."); 80 ProducerConsumerDemo.LOCK.wait(); 81 } catch (InterruptedException e) { 82 e.printStackTrace(); 83 } 84 } 85 86 // 如果队列不空,则正常消费 87 ProducerConsumerDemo.QUEUE.remove(0); 88 System.out.println("线程【" + Thread.currentThread().getName() + 89 "】消费了数据.当前队列size:" + ProducerConsumerDemo.QUEUE.size()); 90 91 // 唤醒生产者 92 ProducerConsumerDemo.LOCK.notifyAll(); 93 } 94 } 95 } 96 }
1 1.你知道wait/notify/notifyAll方法的作用吗? 2 1.1.wait/notify/notifyAll都是Object中的方法 3 1.2.通过等待与唤醒,实现线程之间的协同 4 1.3.wait方法会释放当前锁对象,让线程进入WAITING状态 5 1.4.notify方法用于: 6 1.4.1.唤醒当前锁对象等待池(WaitSet)中,正在等待 7 当前锁对象的某一个线程 8 1.4.2.让该线程进入RUNNABLE状态,并移入锁池(EntrySet)中, 9 重新竞争锁对象 10 1.5.notifyAll方法用于: 11 1.5.1.唤醒当前锁对象等待池(WaitSet)中,正在等待 12 当前锁对象的所有线程 13 1.5.2.让等待当前锁对象的所有线程,进入RUNNABLE状态,并 14 移入锁池(EntrySet)中,重新竞争锁对象 15 16 2.你知道notify与notifyAll方法的区别吗? 17 2.1.通过以上1.4点、1.5点已经说明了notify 18 与notifyAll的区别 19 2.2.这里可能有朋友不明白锁池(EntrySet),与 20 等待池(WaitSet)的概念。我们通过一个图进行说明: 21 22 #流程文字描述: 23 1.锁池(EntrySet):代表正在等待同一锁对象的线程集合,线程进入BLOCKED状态 24 2.拥有者(Owner):代表已经获取到锁对象的线程 25 3.等待池(WaitSet):代表已经进入WAITING状态,等待被唤醒的线程集合 26 4.流程描述: 27 4.1.enter:线程准备获取锁对象,此时锁被其它线程占有,线程进入EntrySet 28 4.2.acquire:当其它线程释放锁对象,EntrySet中的某个线程获取占有锁对象 29 4.3.release:占有锁对象线程,通过wait方式释放锁对象,进入WaitSet中,等待被唤醒 30 4.4.acquire:当有其它线程,通过notify/notifyAll方法唤醒WaitSet中线程,线程将重新进入EntrySet,重新竞争获取锁对象 31 4.5.release and exit:占有锁对象线程,释放锁并退出执行,线程生命周期结束
方法:sleep
方法简述:
1.让线程进入TIMED_WAITING状态
2.如果线程占有锁对象资源,不会释放锁
1 import java.util.Random; 2 import java.util.concurrent.TimeUnit; 3 4 /** 5 * 线程方法sleep: 6 * 1.让线程进入TIMED_WAITING状态 7 * 2.如果线程占有锁对象资源,不会释放锁 8 */ 9 public class ThreadMethodSleepDemo { 10 11 // 锁 12 public final static Object LOCK = new Object(); 13 14 public static void main(String[] args) { 15 // 创建Runnable对象 16 Runnable r1 = new MyRunnable(); 17 18 // 创建两个线程对象 19 Thread t1 = new Thread(r1,"thread-0"); 20 Thread t2 = new Thread(r1,"thread-1"); 21 22 // 启动线程 23 t1.start(); 24 t2.start(); 25 26 } 27 } 28 29 /** 30 * 实现Runnable接口,创建线程 31 */ 32 class MyRunnable implements Runnable{ 33 public void run() { 34 System.out.println("【" + Thread.currentThread().getName() + "】准备休眠."); 35 // 加锁,演示sleep不释放锁 36 synchronized (ThreadMethodSleepDemo.LOCK){ 37 System.out.println("【" + Thread.currentThread().getName() + "】获取到锁.休眠进行中."); 38 int time = 0; 39 try { 40 Random random = new Random(); 41 time = random.nextInt(6); 42 TimeUnit.SECONDS.sleep(time); 43 } catch (InterruptedException e) { 44 e.printStackTrace(); 45 } 46 System.out.println("【" + Thread.currentThread().getName() + "】随机休眠:" +time+ "秒时间到.释放锁."); 47 } 48 System.out.println("【" + Thread.currentThread().getName() + "】结束休眠."); 49 } 50 }
方法:join
方法简述:
1.方法join表示在一个线程中,加入另外一个线程
2.被加入线程,会等待【加入线程】执行完成
1 import java.util.concurrent.TimeUnit; 2 3 /** 4 * 线程方法join: 5 * 1.如果有线程加入我们,我们就等待加入线程执行完成 6 */ 7 public class ThreadMethodJoinDemo { 8 9 public static void main(String[] args) throws InterruptedException{ 10 // 创建线程对象 11 Runnable r1 = new MyRunnable1(); 12 Thread t1 = new Thread(r1,"thread-0"); 13 t1.start(); 14 15 // join方法 16 t1.join(); 17 18 System.out.println("主线程main方法执行中."); 19 20 } 21 } 22 23 /** 24 * 实现Runnable接口,创建线程 25 */ 26 class MyRunnable1 implements Runnable{ 27 public void run() { 28 for(int i = 0; i < 3; i++){ 29 System.out.println(Thread.currentThread().getName() + 30 "第:【" + i + "】次执行."); 31 32 // 休眠10毫秒 33 try { 34 TimeUnit.MILLISECONDS.sleep(10); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 } 39 System.out.println(Thread.currentThread().getName() + "执行结束."); 40 } 41 }
方法:yield
方法简述:
1.线程主动让出CPU执行时间
2.孔融让梨,和谐社会大家好
3.需要注意:这只是一种美德,不能对它产生依赖
1 /** 2 * 线程方法yield: 3 * 1.线程主动让出CPU执行时间 4 * 2.孔融让梨,和谐社会大家好 5 */ 6 public class ThreadMethodYieldDemo { 7 8 public static void main(String[] args) { 9 // 创建两个线程 10 Runnable r1 = new MyRunnable2(); 11 12 Thread t0 = new Thread(r1, "thread-0"); 13 Thread t1 = new Thread(r1, "thread-1"); 14 15 // 线程t0 16 t0.start(); 17 18 // yield方法,线程t1 19 t1.start(); 20 t1.yield(); 21 22 } 23 } 24 25 /** 26 * 实现Runnable接口,创建线程 27 */ 28 class MyRunnable2 implements Runnable{ 29 public void run() { 30 for(int i = 0; i < 3; i++){ 31 System.out.println("【" + Thread.currentThread().getName() + 32 "】第:【" + i + "】次执行."); 33 34 } 35 System.out.println("【" + Thread.currentThread().getName() + 36 "】执行结束."); 37 } 38 }
1.你知道sleep方法的作用吗? 1.1.让线程进入TIMED_WAITING状态 1.2.如果线程占有锁对象资源,不会释放锁 2.你知道wait与sleep方法的区别吗? 2.1.wait方法,让线程进入WAITING状态,或者TIMED_WAITING状态 2.2.sleep方法,让线程进入TIMED_WAITING状态 2.3.区别: wait方法,会释放占有的锁对象资源 sleep方法,【不】释放占有的锁对象资源 3.你知道join方法的作用吗? 3.1.join表示在一个线程中,加入另外一个线程 3.2.被加入线程,会等待【加入线程】执行完成 4.你知道yield方法的作用吗? 4.1.线程主动让出CPU执行时间 4.2.孔融让梨,和谐社会大家好 4.3.需要注意: 该方法,只是一种美德,我们不应该依赖它
六、线程属性
在我们日常多线程编程中,需要关心线程的线程优先级有: 线程Id 线程名称 是否是守护线程 线程优先级
1、线程Id
简述:
1.线程Id,是线程的唯一身份标识
2.用于JVM执行过程中,识别线程
1 /** 2 * 线程属性:Id 3 * 1.线程Id,是线程的唯一身份标识 4 * 2.用于JVM执行过程中,识别线程 5 */ 6 public class ThreadIdDemo { 7 8 public static void main(String[] args) { 9 // 创建线程对象 10 Runnable r1 = new MyRunnable(); 11 Thread t1 = new Thread(r1); 12 t1.start(); 13 14 // 打印主线程main的Id 15 System.out.println("【主】线程Id:" + Thread.currentThread().getId()); 16 } 17 18 } 19 20 /** 21 * 实现Runnable接口,创建线程 22 */ 23 class MyRunnable implements Runnable{ 24 public void run() { 25 System.out.println("【子】线程Id:" + Thread.currentThread().getId()); 26 } 27 }
2、线程名称
简述:
1.线程名称,可以在创建线程对象的时候设置(有默认名称)
2.用于在开发过程中,方便调试,或者友好阅读
1 import java.util.concurrent.TimeUnit; 2 3 /** 4 * 线程属性:Id 5 * 1.线程名称,可以在创建线程对象的时候设置(有默认名称) 6 * 2.用于在开发过程中,方便调试,或者友好阅读 7 */ 8 public class ThreadNameDemo { 9 10 public static void main(String[] args) throws InterruptedException{ 11 // 创建两个线程对象 12 Runnable r1 = new MyRunnable1(); 13 // 使用默认线程名称 14 Thread t1 = new Thread(r1); 15 t1.start(); 16 17 // 休眠1秒,实现顺序打印 18 TimeUnit.SECONDS.sleep(1); 19 20 // 指定线程名称 21 Thread t2 = new Thread(r1,"my-thread-2"); 22 t2.start(); 23 } 24 25 } 26 27 /** 28 * 实现Runnable接口,创建线程 29 */ 30 class MyRunnable1 implements Runnable{ 31 public void run() { 32 System.out.println("线程名称:" + Thread.currentThread().getName()); 33 } 34 }
1.你知道线程Id的作用吗? 1.1.线程Id,是线程的唯一身份标识 1.2.用于JVM执行过程中,识别线程 2.你知道线程名称的作用吗? 2.1.线程名称,可以在创建线程对象的时候设置(有默认名称) 2.2.用于在开发过程中,方便调试,或者友好阅读 3.你知道什么是守护线程吗? 3.1.守护线程,是在后台运行的线程 4.你知道守护线程与用户线程的区别吗? 4.1.我们平常开发,创建的都是用户线程 4.2.守护线程,与用户线程总体没有区别 4.3.区别在于: 4.3.1.守护线程,不会影响JVM的退出执行 4.3.2.用户线程,当JVM退出的时候,需要等待用户线程执行完成 5.你知道有哪些线程优先级吗? 5.1.线程优先级,表示获取CPU执行时间机会的多少,优先级越高,越有机会 5.2.在java编程语言中,总共有10种优先级设置: 最小优先级:1 默认优先级:5 最大优先级:10 6.你知道为什么我们在实际开发中,不应该依赖线程优先级吗? 6.1.原因一: java的线程,是基于操作系统内核线程实现 6.2.原因二: 不同的操作系统,优先级定义不一样 6.3.原因三: 在不同的操作系统上,java线程优先级映射到操作系统内核会不一样,因此我们不应该依赖线程的优先级
七、异常处理
1、难以发现的子线程异常
简述:
1.在子线程child-exception-0中,抛出异常
2.主线程main依然正常执行,在实际项目中,会导致难以发现子线程的异常情况
1 /** 2 * 主线程main不能发现子线程异常 3 */ 4 public class NotFoundChildThreadException { 5 6 public static void main(String[] args) { 7 // 创建线程对象 8 Runnable r1 = new MyRunnable(); 9 Thread t1 = new Thread(r1,"child-exception-0"); 10 t1.start(); 11 12 // 主线程循环打印输出,忽略子线程异常 13 for (int i = 0; i < 5; i++) { 14 System.out.println("主线程main输出:风景这边独好!当前索引【" 15 + i + "】"); 16 } 17 18 } 19 20 } 21 22 /** 23 * 实现Runnable,创建线程 24 */ 25 class MyRunnable implements Runnable{ 26 public void run() { 27 System.out.println(Thread.currentThread().getName() + 28 "准备抛出异常了...start"); 29 // 抛出异常 30 throw new RuntimeException("子线程抛出了异常."); 31 } 32 }
2、不能捕获的子线程异常
简述:
1.创建3个子线程:thread-0、thread-1,thread-2
2.每个子线程都会抛出异常
3.在主线程main中,进行捕获处理。期望如果thread-0抛出了异常,那么thread-1/thread-2线程,不要创建执行
1 /** 2 * 不能捕获的子线程异常 3 */ 4 public class NotCaughtChildException { 5 6 public static void main(String[] args) { 7 // 预期子线程会抛出异常,通过try{}catch(){}捕获处理 8 try{ 9 // 创建子线程0 10 Runnable r1 = new MyRunnable1(); 11 Thread t0 = new Thread(r1,"thread-0"); 12 t0.start(); 13 14 // 创建子线程1 15 Thread t1 = new Thread(r1,"thread-1"); 16 t1.start(); 17 18 // 创建子线程2 19 Thread t2 = new Thread(r1,"thread-2"); 20 t2.start(); 21 22 }catch (RuntimeException e){ 23 System.out.println("捕获到了异常."); 24 } 25 } 26 } 27 28 /** 29 * 实现Runnable,创建线程 30 */ 31 class MyRunnable1 implements Runnable{ 32 public void run() { 33 System.out.println(Thread.currentThread().getName() + 34 "准备抛出异常了...start"); 35 // 抛出异常 36 throw new RuntimeException("子线程抛出了异常."); 37 } 38 }
3、全局异常处理
简述:
1.在2.中,不能通过try{}catch(){}捕获子线程异常。因为try{}catch(){}只能捕获当前线程的异常
2.如果要对所有子线程,进行统一异常处理,需要一个全局异常处理器
3.全局异常处理器接口:Thread.UncaughtExceptionHandler
4.1.编写全局异常处理器,实现接口:Thread.UncaughtExceptionHandler
4.2.注册使用全局异常处理器
3.1 全局异常处理器:
1 /** 2 * 自定义全局异常处理器类 3 */ 4 public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 5 6 public void uncaughtException(Thread t, Throwable e) { 7 System.out.println(t.getName() + "发生了异常,异常消息:" + e.getMessage()); 8 } 9 }
3.2 注册使用全局异常处理器
1 /** 2 * 注册使用自定义全局异常处理器 3 */ 4 public class UseUncaughtExceptionHandler { 5 6 public static void main(String[] args) { 7 8 // 关键代码:设置全局异常处理器 9 Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); 10 11 // 创建子线程0 12 Runnable r1 = new MyRunnable1(); 13 Thread t0 = new Thread(r1,"thread-0"); 14 t0.start(); 15 16 // 创建子线程1 17 Thread t1 = new Thread(r1,"thread-1"); 18 t1.start(); 19 20 // 创建子线程2 21 Thread t2 = new Thread(r1,"thread-2"); 22 t2.start(); 23 } 24 } 25 26 /** 27 * 实现Runnable,创建线程 28 */ 29 class MyRunnable2 implements Runnable{ 30 public void run() { 31 System.out.println(Thread.currentThread().getName() + 32 "准备抛出异常了...start"); 33 // 抛出异常 34 throw new RuntimeException("子线程抛出了异常."); 35 } 36 }
1.你知道java的异常体系吗? 1.1.java异常体系中,老祖宗是Throwable 1.2.在Throwable下,有Exception异常体系(日常开发中,见得最多) 1.3.在Throwable下,有Error错误体系(日常开发中,较少关注) 2.你知道哪一种异常处理方式比较好吗? 2.1.通过案例演示,我们知道不能通过try{}catch(){},跨线程捕获异常。try{}catch(){}只能捕获当前线程自己的异常 2.2.处理方式一: 2.2.1.可以在当前线程中进行try{}catch(){},捕获处理当前线程的异常 2.2.2.该种方式处理起来代码量多、繁琐。不推荐使用 2.3.处理方式二: 2.3.1.通过设置全局异常处理器:UncaughtExceptionHandler 2.3.2.实现异常全局统一处理。推荐使用 3.你知道如何使用UncaughtExceptionHandler吗? 3.1.编写全局异常处理器,实现接口: Thread.UncaughtExceptionHandler 3.2.注册使用全局异常处理器: // 关键代码:设置全局异常处理器 Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
Java异常体系类图
1.你知道多线程的理论基础有哪些吗? 1.1.进程与线程的区别 1.2.线程实现方式 1.3.线程安全三要素 1.4.java内存模型JMM 1.5.锁 2.你知道进程与线程的区别吗? 2.1.进程是操作系统【分配资源】的最小单位 2.2.线程是操作系统【调度】的最小单位 3.你知道线程的实现方式有哪些吗? 3.1.基于操作系统内核实现方式(内核线程) 3.2.基于用户进程实现方式(用户态线程,即协程) 3.3.java的线程实现方式是:内核线程实现方式 4.你知道多线程安全的三要素吗? 4.1.线程安全要素一:原子性 4.2.线程安全要素二:可见性 4.3.线程安全要素三:有序性 5.你知道java的内存模型JMM吗? 5.1.参见附图 6.你知道java编程中,线程安全的常规手段吗? 6.1.线程安全常规手段一:加锁 6.2.线程安全常规手段二:消除共享资源 7.你知道java中的volatile关键字吗? 7.1.volatile关键字是一种轻量级线程安全实现方式 7.2.volatile关键字的底层原理:保证可见性,禁止重排序 7.3.使用volatile关键字注意事项: a.volatile关键字修饰变量值修改,不依赖原来的值;或者只有单一线程进行修改 b.volatile关键字修饰的变量,不与其它变量一起参与原子性约束 c.满足a、b两条,那么volatile关键字修饰的变量,在多线程下是线程安全的
java内存模型JMM: