Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常

前言 继续学习Java多线程基础与使用详细篇(一)----线程创建与线程停止下的知识。
本篇会涉及多线程的生命周期、Thread和Object类中的重要方法详解、线程的各个属性、
未捕获异常如何处理。
一、线程的六个状态(生命周期) 1.1 每个状态的含义是 (1).NEW
新创建了一个线程对象,但还没有调用start()方法
(2).Runnable
Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
(3).Blocked
表示线程阻塞于锁。例如被同步代码块拿走了锁后会被阻塞。
(4).Waiting
进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
(5).Timed Waiting
该状态不同于WAITING,它可以在指定的时间后自行返回。
(6).Terminated
终止(TERMINATED):表示该线程已经执行完毕。
1.2 状态间的转化图示 Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常
文章图片
image.png 1.3 演示线程的NEW、RUNNBALE、Terminated状态 展示线程的NEW、RUNNABLE、Terminated状态。即使是正在运行,也是Runnable状态,而不是Running。

public class NewRunnableTerminated implements Runnable {public static void main(String[] args) { Thread thread = new Thread(new NewRunnableTerminated()); //打印出NEW的状态 System.out.println(thread.getState()); thread.start(); System.out.println(thread.getState()); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //打印出RUNNABLE的状态,即使是正在运行,也是RUNNABLE,而不是RUNNING System.out.println(thread.getState()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //打印出TERMINATED状态 System.out.println(thread.getState()); }@Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(i); } } } 结果: NEW RUNNABLE RUNNABLE 0 1 2 3 4 .... TERMINATED

1.4 展示Blocked,Waiting,TimedWaiting
public class BlockedWaitingTimedWaiting implements Runnable{ public static void main(String[] args) { BlockedWaitingTimedWaiting runnable = new BlockedWaitingTimedWaiting(); Thread thread1 = new Thread(runnable); thread1.start(); Thread thread2 = new Thread(runnable); thread2.start(); try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } //打印出Timed_Waiting状态,因为正在执行Thread.sleep(1000); System.out.println(thread1.getState()); //打印出BLOCKED状态,因为thread2想拿得到sync()的锁却拿不到 System.out.println(thread2.getState()); try { Thread.sleep(1300); } catch (InterruptedException e) { e.printStackTrace(); } //打印出WAITING状态,因为执行了wait() System.out.println(thread1.getState()); }@Override public void run() { syn(); }private synchronized void syn() { try { Thread.sleep(1000); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } 结果: TIMED_WAITING BLOCKED WAITING

1.5 阻塞状态是什么 (1).一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态
(2). 不仅仅是Blocked
1.6. 线程生命周期总结 线程有六种状态,生命周期是什么,这些都可以看上面的含义和画图看出来,或者通过网上学习更加深入的知识。
2. Thread和Object类中线程相关方法 2.1 方法概览 Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常
文章图片
image.png 2.1 wait,notify,notifAll 作用、用法:
阻塞阶段
直到以下4种情况之一发生时,才会被唤醒 wait
(1). 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程;
(2).另一个线程调用这个对象的notifyAll()方法
(3).过了wait(long timeout)规定的超时时间,如果传入0就永久等待
(4).线程自身调用了interrupt(),也会唤醒
唤醒阶段:notify notifyAll
notify 一个 ,随机
notifyall 所有
遇到中断
2.2 wait和notify的基本用法 普通用法:
主要是阅读代码的执行顺序,了解wait是用来释放锁的,用一个代码演示,从代码可以看出线程一是用来释放锁,并睡眠2毫秒,这段实践线程二刚好唤醒锁线程一并获取了锁。
public class Wait { publicstatic Object object = new Object(); //线程一 static class Thread1 extends Thread{ @Override public void run() { synchronized (object){ System.out.println(Thread.currentThread().getName()+"开始执行了..."); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+ "获取到了锁。"); }} }// 线程二 static class Thread2 extends Thread{ @Override public void run() { synchronized (object){ object.notify(); System.out.println("线程"+Thread.currentThread().getName()+"调用notify..."); }} } public static void main(String args[]) throws InterruptedException { Thread1 thread1 = new Thread1(); Thread2 thread2 = new Thread2(); thread1.start(); Thread.sleep(200); thread2.start(); } } Thread-0开始执行了... 线程Thread-1调用notify... 线程Thread-0获取到了锁。

2.3 notify和notifyAll 使用3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
notify 只会唤醒一条线程,于是导致第二条线程没有唤醒一直永久的等待。
notifyAll 唤醒所有的。
public class WaitNotifyAll implements Runnable {private static final Object resourceA = new Object(); public static void main(String[] args) throws InterruptedException { Runnable r = new WaitNotifyAll(); Thread threadA = new Thread(r); Thread threadB = new Thread(r); Thread threadC = new Thread(new Runnable() { @Override public void run() { synchronized (resourceA) { resourceA.notifyAll(); //resourceA.notify(); System.out.println("ThreadC notified."); } } }); threadA.start(); threadB.start(); //Thread.sleep(200); threadC.start(); } @Override public void run() { synchronized (resourceA) { System.out.println(Thread.currentThread().getName()+" got resourceA lock."); try { System.out.println(Thread.currentThread().getName()+" waits to start."); resourceA.wait(); System.out.println(Thread.currentThread().getName()+"'s waiting to end."); } catch (InterruptedException e) { e.printStackTrace(); } } } } 此时会获取两种结果 第一种是完美的执行,并不会去阻塞线程 Thread-0 got resourceA lock. Thread-0 waits to start. Thread-1 got resourceA lock. Thread-1 waits to start. ThreadC notified. Thread-1's waiting to end. Thread-0's waiting to end. 第二种是会出现阻塞现况 Thread-0 got resourceA lock. Thread-0 waits to start. ThreadC notified. Thread-1 got resourceA lock. Thread-1 waits to start. Thread-0's waiting to end.

2.4 只释放当前monitor展示 证明wait只释放当前的那把锁,只释放当前monitor展示,每一个锁都是一一对应的关系。
下面是说 线程一会持有A,B对象,然后就释放A,此时还持有B,接下来线程二是很顺利的获取A的资源,却没有获取B的资源,因为B的资源被线程一持有并没有释放
public class WaitNotifyReleaseOwnMonitor {private static volatile Object resourceA = new Object(); private static volatile Object resourceB = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { synchronized (resourceA) { System.out.println("ThreadA got resourceA lock."); synchronized (resourceB) { System.out.println("ThreadA got resourceB lock."); try { System.out.println("ThreadA releases resourceA lock."); resourceA.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (resourceA) { System.out.println("ThreadB got resourceA lock."); System.out.println("ThreadB tries to resourceB lock."); synchronized (resourceB) { System.out.println("ThreadB got resourceB lock."); } } } }); thread1.start(); thread2.start(); } }

2.5 wait、notify、notifyAll特点、性质 1、 必须先拥有monitor
2、 只能唤醒其中一个
3、 属于object类 ->所有对象的引入
4、 类似功能的Condition wait、notify类似
5、 同时持有多个锁的情况 -> 注意:释放的顺序->获取的顺序
2.6. 用wait/notify来实现生产者消费者模式 EventStorage LinkedList表用来实现生产数据和存储数据方法,通过生产者给消费者消费
public class ProducerConsumerModel { public static void main(String[] args) { EventStorage eventStorage = new EventStorage(); Producer producer = new Producer(eventStorage); Consumer consumer = new Consumer(eventStorage); new Thread(producer).start(); new Thread(consumer).start(); } }class Producer implements Runnable {private EventStorage storage; public Producer( EventStorage storage) { this.storage = storage; }@Override public void run() { for (int i = 0; i < 100; i++) { storage.put(); } } }class Consumer implements Runnable {private EventStorage storage; public Consumer( EventStorage storage) { this.storage = storage; }@Override public void run() { for (int i = 0; i < 100; i++) { storage.take(); } } }class EventStorage {private int maxSize; private LinkedList storage; public EventStorage() { maxSize = 10; storage = new LinkedList<>(); }public synchronized void put() { while (storage.size() == maxSize) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.add(new Date()); System.out.println("仓库里有了" + storage.size() + "个产品。"); notify(); }public synchronized void take() { while (storage.size() == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size()); notify(); } }

2.7 用wait和notify实现两个线程交替打印0~100的奇偶数 由此发现为什么wait需要放在同步代码块中,是为了让通信变得可靠、防止死锁、等待的发生。如果没有代码块的保护,会随时被notify切换过去。
而是sleep本身是针对自己,和其它线程关系并不大。
public class WaitNotifyPrintOddEveWait {private static int count = 0; private static final Object lock = new Object(); public static void main(String[] args) { new Thread(new TurningRunner(), "偶数").start(); new Thread(new TurningRunner(), "奇数").start(); }//1. 拿到锁,我们就打印 //2. 打印完,唤醒其他线程,自己就休眠 static class TurningRunner implements Runnable { @Override public void run() { while (count <= 100) { synchronized (lock) { //拿到锁就打印 System.out.println(Thread.currentThread().getName() + ":" + count++); lock.notify(); if (count <= 100) { try { //如果任务还没结束,就让出当前的锁,并休眠 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } }

2.8 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里,而Sleep定义在Thread类里 1.notify()和notifyAll()是属于一个对象的,事实上就是绑定到一个对象的锁,
2.wait方法是属于Object对象,那调用Thrad.wait本身就不适合
3.用supend()和resume()来阻塞线程可以吗:不推荐,推荐使用wait /notify
3.sleep方法 作用 : 我只想让现场在预期的时间执行,其他时候不再占用CPU资源
3.1 sleep 方法不释放锁 包括 synchronized和lock, 和wait不同
使用synchronized代码演示
展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
public class SleepDontReleaseMonitor implements Runnable {public static void main(String[] args) { SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor(); new Thread(sleepDontReleaseMonitor).start(); new Thread(sleepDontReleaseMonitor).start(); }@Override public void run() { syn(); }private synchronized void syn() { System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块"); } } 线程Thread-0获取到了monitor。 线程Thread-0退出了同步代码块 线程Thread-1获取到了monitor。 线程Thread-1退出了同步代码块

使用lock代码演示
sleep不释放lock(lock需要手动释放)
public class SleepDontReleaseLock implements Runnable {private static final Lock lock = new ReentrantLock(); @Override public void run() { lock.lock(); System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁"); try { Thread.sleep(5000); System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }public static void main(String[] args) { SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock(); new Thread(sleepDontReleaseLock).start(); new Thread(sleepDontReleaseLock).start(); } } 线程Thread-0获取到了锁 线程Thread-0已经苏醒 线程Thread-1获取到了锁 线程Thread-1已经苏醒

3.2 sleep 方法响应中断 sleep方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间,后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。
Thread.sleep() 方法小于0会抛出异常
TimeUnit.SECONDS.sleep() 小于不会抛出异常,推荐这种
public class SleepInterrupted implements Runnable{public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new SleepInterrupted()); thread.start(); Thread.sleep(6500); thread.interrupt(); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(new Date()); try { TimeUnit.HOURS.sleep(3); TimeUnit.MINUTES.sleep(25); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { System.out.println("我被中断了!"); e.printStackTrace(); } } } } Sat Sep 05 10:20:43 CST 2020 我被中断了! Sat Sep 05 10:20:50 CST 2020 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:51) at java.lang.Thread.run(Thread.java:748)

3.3 sleep方法总结 wait/notify/sleep 异同(方法属于哪个对象,线程状态怎么切换)
相同
阻塞
响应中断
不同
同步方法中
释放锁
指定时间
所属类
4. join方法 作用: 因为新的线程加入了我们,所以我们要等他执行完再出发
用法:main等待thread1执行完毕,注意谁等谁
4.1 普通用法代码演示 演示join,注意语句输出顺序,会变化。
public class Join { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); } }); thread.start(); thread2.start(); System.out.println("开始等待子线程运行完毕"); thread.join(); thread2.join(); System.out.println("所有子线程执行完毕"); } } 开始等待子线程运行完毕 Thread-1执行完毕 Thread-0执行完毕 所有子线程执行完毕

4.2 遇到中断 演示join期间被中断的效果
public class JoinInterrupt { public static void main(String[] args) { Thread mainThread = Thread.currentThread(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { mainThread.interrupt(); Thread.sleep(5000); System.out.println("Thread1 finished."); } catch (InterruptedException e) { System.out.println("子线程中断"); } } }); thread1.start(); System.out.println("等待子线程运行完毕"); try { thread1.join(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"主线程中断了"); thread1.interrupt(); } System.out.println("子线程已运行完毕"); } } 等待子线程运行完毕 main主线程中断了 子线程已运行完毕 子线程中断

4.3 在join期间,线程到底是什么状态 Waiting
public class JoinThreadState { public static void main(String[] args) throws InterruptedException { Thread mainThread = Thread.currentThread(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); System.out.println(mainThread.getState()); System.out.println("Thread-0运行结束"); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); System.out.println("等待子线程运行完毕"); thread.join(); System.out.println("子线程运行完毕"); } } 等待子线程运行完毕 WAITING Thread-0运行结束 子线程运行完毕

4.4 Join 原理 通过源码可以看到Join内部是有去调用wait方法
public final void join() throws InterruptedException { join(0); } 一开始是0,休眠时间是无线,直到唤醒或者中断或者前面线程执行完毕, public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }

5. yield方法 作用:释放CPU时间片
定位:JVM不保证遵循
yield和sleep区别:是否随时可能再次被调度
6.获取当前执行线程的引用:Thread.currentThread 演示打印main, Thread-0, Thread-1
public class CurrentThread implements Runnable {public static void main(String[] args) { new CurrentThread().run(); new Thread(new CurrentThread()).start(); new Thread(new CurrentThread()).start(); }@Override public void run() { System.out.println(Thread.currentThread().getName()); } } main Thread-0 Thread-1

7.线程的各属性总结 Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常
文章图片
image.png 7.1 演示id代码 ID从1开始,JVM运行起来后,我们自己创建的线程的ID早已不是2
public class Id {public static void main(String[] args) { Thread thread = new Thread(); System.out.println("主线程的ID"+Thread.currentThread().getId()); System.out.println("子线程的ID"+thread.getId()); } }

7.2 守护线程 1.作用:给用户线程提供服务
2.3个特性:
1. 线程类型默认继承自父线程
2. 被谁启动
3. 不影响退出JVM
3.守护线程和普通线程的区别
1. 整体上无区别
2. 唯一区别在于JVM 的离开
7.3 守护线程和普通线程的区别总结 1.他整体没什么区别,区别是否会影响退出,一个为逻辑的,一个为服务我们的
2.我们是否需要给线程设置为守护线程
我们不应该把线程设置成守护线程,会导致正在访问文件期间,
就会关掉,操作的过程会强行的终止。
开发过程中没必要去设置。
7.4 线程优先级
1.10个级别,默认5 2.程序设计不应依赖于优先级 3. 不同操作系统不一样 4. 优先级会被操作系统改变

8. 未捕获异常如何处理 8.1 为什么需要UncaughtExceptionHandler (1).主线程可以轻松发现异常,子线程却不行 ExceptionInChildThread###### 此代码演示,子线程会出现异常,但是主线程还是会继续执行下去
public class ExceptionInChildThread implements Runnable {public static void main(String[] args) { new Thread(new ExceptionInChildThread()).start(); for (int i = 0; i < 1000; i++) { System.out.println(i); } }@Override public void run() { throw new RuntimeException(); } }

(2).子线程异常无法用传统方法捕获 例如一下代码:
  1. 不加try catch抛出4个异常,都带线程名字
  2. 加了try catch,期望捕获到第一个线程的异常,线程234不应该运行,希望看到打印出Caught Exception
  3. 执行时发现,根本没有Caught Exception,线程234依然运行并且抛出异常
    这样说明不能直接捕获的后果。
    下面是使用了手动:在每个run方法里进行try catch
public class CantCatchDirectly implements Runnable {public static void main(String[] args) throws InterruptedException { try { new Thread(new CantCatchDirectly(), "MyThread-1").start(); Thread.sleep(300); new Thread(new CantCatchDirectly(), "MyThread-2").start(); Thread.sleep(300); new Thread(new CantCatchDirectly(), "MyThread-3").start(); Thread.sleep(300); new Thread(new CantCatchDirectly(), "MyThread-4").start(); } catch (RuntimeException e) { System.out.println("Caught Exception."); }}@Override public void run() { try { throw new RuntimeException(); } catch (RuntimeException e) { System.out.println("Caught Exception."); } } } Caught Exception. Caught Exception. Caught Exception. Caught Exception.

8.2 利用UncaughtExceptionHandler接口(推荐) 1. UncaughtExceptionHandler接口 2. void uncaughtException(Thread t, Throwable e)###### 3. 异常处理器的调用策略 代码示例
public void uncaughtException(Thread t, Throwable e) { //默认情况下parent是null if (parent != null) { parent.uncaughtException(t, e); } else { // 调用Thread.setDefaultUncaughtExceptionHandler(); //方法设置的全局handler进行处理 Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { // 全局 handler 也不存在就输出异常栈 System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } } }

4.实现自己的UncaughtExceptionHandler
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {private String name; public MyUncaughtExceptionHandler(String name) { this.name = name; }@Override public void uncaughtException(Thread t, Throwable e) { Logger logger = Logger.getAnonymousLogger(); logger.log(Level.WARNING, "线程异常,终止啦" + t.getName()); System.out.println(name + "捕获了异常" + t.getName() + "异常"); } }

5. 调用上面UncaughtExceptionHandler
public class UseOwnUncaughtExceptionHandler implements Runnable {public static void main(String[] args) throws InterruptedException { Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1")); new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-1").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-2").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-3").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-4").start(); }@Override public void run() { throw new RuntimeException(); } } 警告: 线程异常,终止啦MyThread-1 捕获器1捕获了异常MyThread-1异常 九月 05, 2020 3:16:38 下午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: 线程异常,终止啦MyThread-2 捕获器1捕获了异常MyThread-2异常 捕获器1捕获了异常MyThread-3异常 九月 05, 2020 3:16:38 下午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: 线程异常,终止啦MyThread-3 九月 05, 2020 3:16:39 下午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: 线程异常,终止啦MyThread-4 捕获器1捕获了异常MyThread-4异常

8.4 线程未捕获异常总结 1.Java 异常体系 Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常
文章图片
image.png 2.实际工作中,如何全家处理异常? 为什么要全局护理?不处理行不行 用一个全局的UncaughtExceptionHandler,我们就可以根据自己的业务处理和前端和后端日志处理
3.run方法是否可以抛出异常?如果抛出异常,线程状态会怎样 如果提前没有 catch异常,就会抛出异常,线程就会终止
4. 线程中如何处理某个未处理异常? 用一个全局处理器
【Java多线程基础与使用详细篇(二)----线程生命周期与对象中的重要方法及各个属性与处理捕获异常】9.总结
大致上就把Java 多线程的八大核心后面的四个学习了解,这是用看某学习视频总结而来的个人学习文章。希望自己也能对Java多线基础巩固起来。

    推荐阅读