文章目录
Java 多线程
一、多线程概述
- 线程(Thread)是一个
程序内部的一条执行流程
。 - 程序如果有一条执行流程,就是
单线程程序
。 - 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由 CPU 负责调度执行)。
二、 多线程创建方式
Java 是通过 java.lang.Thread 类的对象代表多线程。主要方式有三种 :
-
继承 Thread 类,重写 run 方法
-
实现 Runnable 接口
-
实现 Callable 接口
1、继承 Thread 类创建线程
- 创建子类 MyThread 继承 Thread 线程类
- 重写 Thread 类的 run 方法
- 创建的 MyThread 线程类的对象代表一个线程
- 调用 start() 方法启动线程(自动执行 run 方法)
示例代码:
/**
* 1、创建子类 MyThread 继承 Thread 线程类
*/
public class MyThread extends Thread{
// 2、重写 Thread 类的 run 方法
@Override
public void run() {
super.run();
// 描述线程的执行任务
for (int i = 1; i < 9; i++) {
System.out.println("子线程 MyThread 输出:" + i);
}
}
}
public class ThreadTest1 {
// main 方法是由一条默认的主线程负责执行
public static void main(String[] args) {
// 3、创建的 MyThread 线程类的对象代表一个线程
Thread t = new MyThread();
// 4、启动线程(自动执行 run 方法)
t.start(); // main线程 t线程
for (int i = 1; i < 9; i++) {
System.out.println("主线程 main 输出:" + i);
}
}
}
测试结果:
继承 Thread 类创建线程 优缺点:
- 优点:编码简单
- 缺点:继承了 Thread 类无法继承其他类,不利于功能扩展
多线程注意事项:
- 启动线程必须使用 start 方法,不是 run 方法
如果调用 run 方法,那么 Thread t = new MyThread()
就会把 t 仅仅当做一个 Java 对象而不是一个线程,此时就只有一个 main 线程,程序会先执行 run 方法然后执行下面的代码;
start 方法是向 CPU 注册,告诉 CPU Thread t = new MyThread()
是一个单独的执行流程。
- 不要把主线程任务放在启动子线程之前,否则永远先执行主线程再执行子线程
2、实现 Runnable 接口
- 定义任务类,实现 Runnable 接口
- 重写 run 方法
- 创建任务对象
- 把任务对象交给线程对象处理
代码示例:
/**
* 1、定义任务类,实现 Runnable 接口
*/
public class MyRunnable implements Runnable {
// 2、重写 run 方法
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程输出=== " + i);
}
}
}
public static void main(String[] args) {
// 3、创建任务对象
Runnable target = new MyRunnable();
// 4、把任务对象交给线程对象处理
// public Thread(Runnable target)
new Thread(target).start();
for (int i = 1; i < 9; i++) {
System.out.println("主线程 main 输出:" + i);
}
}
测试结果:
实现 Runnable 接口的匿名内部类写法
public class ThreadTest2_2 {
public static void main(String[] args) {
// 直接创建 Runnable 接口的匿名内部类(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程输出=== " + i);
}
}
};
new Thread(target).start();
// 简化形式1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程2输出=== " + i);
}
}
}).start();
// 简化形式2
new Thread(() -> {
for (int i = 1; i < 9; i++) {
System.out.println("子线程3输出=== " + i);
}
}).start();
for (int i = 1; i < 9; i++) {
System.out.println("主线程输出:" + i);
}
}
}
3、实现 Callable 接口
假如线程执行完毕后需要一些数据的返回,前面两种方式重写的 run 方法均不能返回结果。
此时可以通过 Callable 接口和 FutureTask 类
来实现。
通过源码可以看出来,@FunctionalInterface
表示他是一个函数式接口,同时定义了泛型与 call 方法泛型一致。如果在实现 Callable 的时候定义了泛型则重写 call 方法的返回类型返回类型固定,否则返回 Object
代码示例:
import java.util.concurrent.Callable;
/**
* 1、定义任务类,实现 Callable 接口
*/
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
// 描述线程任务,返回线程执行完毕后返回的结果
// 需求:求 1-n 的和
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "线程求出了1-" + n + "的和是:" + sum;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3、创建一个 Callable 对象
Callable<String> call = new MyCallable(100);
// 4、把 Callable 的对象封装成一个 FutureTask 对象(任务对象)
// 未来任务对象作用?
// ①是一个任务对象,实现了 Runnable 对象
// ②可以在线程执行完毕后,用未来任务对象调用 get 方法获取线程执行完毕后的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 5、把任务对象交给 Thread 对象
new Thread(f1).start();
// 6、获取线程执行完毕后的结果
// 注意:如果执行到这,假如上面的代码没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会继续往下走
String s = f1.get();
System.out.println(s);
}
}
三、Thread 常用的方法
示例代码1:
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
super.run();
Thread t = Thread.currentThread();
for (int i = 1; i <= 3; i++) {
System.out.println(t.getName() + "输出:" + i);
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
Thread t1 = new MyThread("1号线程");
t1.start();
Thread t2 = new MyThread("2号线程");
t2.start();
Thread t = Thread.currentThread();
for (int i = 1; i <= 5; i++) {
System.out.println(t.getName() + "输出:" + i);
}
}
}
示例代码2:sleep 方法
public class ThreadTest4 {
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
// 当 i 等于3时,休眠5秒
if (i == 3) {
// 会让当前执行的线程暂停5秒,再继续执行
// 作用就是让系统运行的慢一些
Thread.sleep(5000);
}
}
}
}
示例代码3:join 方法
public class ThreadTest5 {
public static void main(String[] args) throws InterruptedException {
// join 方法作用:让当前调用这个方法的线程先执行
Thread t1 = new MyThread("1号线程");
t1.start();
t1.join();
Thread t2 = new MyThread("2号线程");
t2.start();
t2.join();
Thread t3 = new MyThread("3号线程");
t3.start();
t3.join();
}
}
测试结果:
四、线程安全
什么是线程安全问题?
线程安全:多个线程,同时操作同一个共享资源
的时候,可能会出现业务安全问题。
通俗来说,比如火车站有100张车票,同时开放三个售票窗口,如果三个窗口同时卖完车票后系统内还剩97张车票,就是线程安全的,否则就是线程不安全。
线程安全问题出现的原因
- 存在多个线程同时执行
- 同时访问一个共享资源
- 存在修改共享资源
程序模拟线程安全
取钱案例
需求:
- 小明和小红是一对夫妻,他们有一个共同账户,余额是10万元,模拟2人同时去取钱10万元
分析:
- 需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户
- 定义一个线程类(用于创建两个线程,分别代表小红和小明)
- 创建2个线程,传入同一个账户对象给2个线程处理
- 启动2个线程,同时去同一个账户对象中取钱10万
账户类:
public class Account {
private double money; // 金额
// 小明 小红同时过来
public void drawMoney(double drawMoney) {
// 先弄清楚谁来取钱
String name = Thread.currentThread().getName();
// 判断余额是否足够
if (this.money >= drawMoney) {
System.out.println(name + "来取钱" + drawMoney + "成功!");
this.money -= drawMoney;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Account() {
}
public Account(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"money=" + money +
'}';
}
}
线程类:
public class DrawMoneyThread extends Thread {
private Account acc;
public DrawMoneyThread(String name, Account acc) {
super(name);
this.acc = acc;
}
@Override
public void run() {
super.run();
// 取钱(小明/小红)
acc.drawMoney(100000);
}
}
执行测试:
public class ThreadTest6 {
public static void main(String[] args) {
// 创建一个账户,代表两个人的共享账户
Account acc = new Account(100000);
// 创建两个线程,分别代表小明和小红,再去同一个账户对象中取10万
new DrawMoneyThread("小明", acc).start(); // 小明
new DrawMoneyThread("小红", acc).start(); // 小红
}
}
测试结果:
五、线程同步
线程同步是解决线程安全问题的方案。
线程同步思想
- 让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的常见方案
- 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
线程同步方式1:同步代码块
- 作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
代码形式:
synchronized(同步锁) {
// 访问共享资源的核心代码
}
- 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可进来执行。
同步锁的注意事项:
- 对于当前同时执行的线程来说,同步锁必须是同一把(
同一个对象
),否则会出 bug
代码示例:
根据模拟线程安全的代码示例,首先需要找到访问共享资源的核心代码
,根据业务逻辑可知,核心代码如下:
因此需要将这段代码加上锁,选中代码然后 IDEA 快捷键 CTRL + ALT + T 选 synchronized,如下:
注意,括号内的 同步加锁
是一个静态变量,表明惟一的一个 Java 对象,说明小明和小红访问的是同一个锁对象
执行结果:
特别注意!!!
由上面代码可以看出,我们定义的锁对象是一个静态字符串,它在系统中永远只有一份,表示唯一锁对象。无论有多少个线程,都只有这一个锁对象锁住线程。
但是如果现在有两个账户(如下图片所示代码),四个线程(多了小黑和小白),当小明线程进来的时候,锁对象会锁住小红、小黑、小白三个线程,但事实是只需要锁住小红的线程就可以了。
所以最佳的方案是小红小明一个锁对象,小黑小白一个锁对象,这个时候,锁对象的定义最好用 this
,如下:
特别地:多个线程调用静态方法
锁对象随便选择一个惟一的对象好不好呢?
- 不好,会影响其他无关线程的执行。
锁对象的使用规范?
使用共享资源作为锁对象
,对于实例方法建议使用this
作为锁对象- 静态方法使用
类名.Class
对象作为锁对象
思考:
- 同步代码块是如何实现线程安全?
- 同步代码块的同步锁对象有什么要求?
线程同步方式2:同步方法
同步方法:
- 作用:把访问共享资源的核心方法给上锁,保证线程安全
- 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
代码示例:
修饰符 synchronized 返回值类型 方法名(形参列表) {
// 操作共享资源的代码
}
仍然以上面的示例为例,在方法上加锁:
同步方法的底层原理:
- 同步方法底层也是有隐式锁对象,只是锁的范围是整个代码块
- 如果方法是实例方法,默认是 this 作为锁对象
- 如果方法是静态方法,默认 类名.Class 作为锁对象
是同步方法块好还是同步方法好一些?
- 范围上:同步代码块锁的范围更小,同步方法锁的范围更大
- 可读性:同步方法更好
思考:
- 同步方法是如何保证线程安全的?
- 同步方法的同步锁对象的原理?
线程同步方式3:Lock 锁
Lock 锁
- Lock 锁:通过它创建出锁对象进行加锁和解锁
- Lock 是接口,不能直接实例化,可以采用他的实现类
ReentrantLock
构建 lock 锁对象
代码示例:
- 在账户类中创建一个锁对象
- 在共享资源处加锁
- 逻辑执行完毕后解锁
package com.xiancheng;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private double money; // 金额
// 在账户类创建一个 Lock 对象
private final Lock lk = new ReentrantLock();
// 小明 小红同时过来
public void drawMoney(double drawMoney) {
// 先弄清楚谁来取钱
String name = Thread.currentThread().getName();
lk.lock(); // 加锁
// 判断余额是否足够
if (this.money >= drawMoney) {
System.out.println(name + "来取钱" + drawMoney + "成功!");
this.money -= drawMoney;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
lk.unlock(); // 解锁
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Account() {
}
public Account(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"money=" + money +
'}';
}
}
注意事项:
- 创建的锁对象要用 final 来修饰
- 解锁逻辑放在 finally 处
原因:加了锁之后一定要解锁,否则中间的逻辑代码出现异常之后永远不能解锁,这样下一个线程就不能进来。所以,逻辑处理的代码最好放在 try catch 里处,如下:
六、线程池
什么是线程池?
- 线程池就是一个可以
复用线程的技术
创建线程池
谁代表线程池?
- 线程池接口:ExecutorService
如何得到线程池对象?
- 使用
ExecutorService
的实现类ThreadPoolExecutor
自创建一个线程池对象 - 使用
Executors
(线程池工具类) 调用方法返回不同特点的线程池对象
ThreadPoolExecutor 构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
参数意义(面试常问):
- corePoolSize:指定线程池的核心线程数量。比如传入3,表示有3个核心线程可以长期复用的
- maximumPoolSize:指定线程池的最大线程数量。(
最大线程数是要大于核心线程数的
)。比如:核心线程是3,最大线程是5,则表示有两个临时线程。 - keepAliveTime:指定临时线程的存活时间。
- unit:临时线程存活的时间单位(秒、分、时、天)。
- workQueue:指定线程池的任务队列。
- threadFactory:指定线程池的线程工厂。用来创建线程的。
- handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处置)
线程池创建的注意事项(面试常问)
1、临时线程什么时候创建?
- 新任务提交时发现
核心线程都在忙,任务队列也满了,并且还可以创建临时线程
,此时才会创建临时线程
2、什么时候开始拒绝新任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来时才会开始拒绝任务
线程池处理 Runnable 任务
代码示例
/**
* 创建 Runnable 任务类
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
// 任务干啥?
System.out.println(Thread.currentThread().getName() + " ===> 输出666");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.concurrent.*;
public class ThreadPoolTest1 {
public static void main(String[] args) {
// 通过 ThreadPoolExecutor 创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
// pool.shutdown(); // 等着线程池任务全部执行完毕,再关闭线程池
// pool.shutdownNow(); // 立即关闭线程池,不管任务是否执行完毕
}
}
测试结果:
如上代码所示,创建了一个:核心线程是3,最大线程5,任务队列4 的线程池。打印结果可以看出有3个核心线程池并且被复用。
但是现在临时线程并没有被创建,只有当核心线程都在忙,任务队列也满了,并且还可以创建临时线程
,此时才会创建临时线程。
修改代码,睡眠时间调大,同时增加任务队列,代码如下:
/**
* 创建 Runnable 任务类
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
// 任务干啥?
System.out.println(Thread.currentThread().getName() + " ===> 输出666");
try {
// 睡眠时间调大
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadPoolTest1 {
public static void main(String[] args) {
// 通过 ThreadPoolExecutor 创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
// 3个核心线程
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
// 4个任务队列
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
// 增加一个任务,此时核心线程都在忙,任务队列也满了
pool.execute(target);
}
}
测试结果:
如下图所示,新创建了一个线程 pool-1-thread-4
关于 new ThreadPoolExecutor.AbortPolicy():任务的拒绝策略
如果3个核心线程在忙,两个临时线程也在忙,4个任务队列占满了,这个时候就会发起任务拒绝。
代码示例:
public class ThreadPoolTest1 {
public static void main(String[] args) {
// 通过 ThreadPoolExecutor 创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
// 3个核心线程
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
// 4个任务队列
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 复用前面的核心线程
// 增加一个任务,此时核心线程都在忙,任务队列也满了
pool.execute(target);
pool.execute(target);
// 3个核心线程在忙,两个临时线程也在忙,4个任务队列占满了,这个时候就会发起任务拒绝
pool.execute(target);
}
}
测试结果:
会抛出异常
常用的任务拒绝策略:
线程池处理 Callable 接口
- 通过 Future submit(Callable task) 执行 Callable 接口
代码示例:
import java.util.concurrent.Callable;
/**
* 1、定义任务类,实现 Callable 接口
*/
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
// 描述线程任务,返回线程执行完毕后返回的结果
// 需求:求 1-n 的和
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "线程求出了1-" + n + "的和是:" + sum;
}
}
import java.util.concurrent.*;
public class ThreadPoolTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 通过 ThreadPoolExecutor 创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Future<String> f1 = pool.submit(new MyCallable(5));
Future<String> f2 = pool.submit(new MyCallable(10));
Future<String> f3 = pool.submit(new MyCallable(15));
Future<String> f4 = pool.submit(new MyCallable(20));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
测试结果:
七、使用 Executors 工具类得到线程池
Executors
- 线程池工具类,提供了很多静态方法用于返回不同特点的线程池对象
注意:以上方法的底层,都是通过线程池的实现类 ThreadPoolExecutor 创建的线程池对象。
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest3 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool1 = Executors.newSingleThreadExecutor();
ExecutorService pool2 = Executors.newCachedThreadPool();
ExecutorService pool3 = Executors.newScheduledThreadPool(3);
}
}
问题:核心线程数量到底要配置多少?
- 计算密集型的任务:核心线程数量 = CPU 的核数 + 1
- IO 密集型的任务:核心线程数量 = CPU 的核数 * 2
CPU 核数:任务管理器中的逻辑处理器
数
特别重要的注意事项!!!!
Executors 使用可能存在的陷阱
- 大型并发系统中使用 Executors 如果不注意可能会存在系统风险。
八、并发和并行
进程
- 正在运行的程序(软件)就是一个独立的进程。
- 线程属于进程,一个进程中可以同时运行多个线程。
- 进程中的多个线程是
并发和并行
执行的。
如下:任务管理器中, IntelliJ IDEA 就是一个进程,这个进程中又同时运行5个线程
并发的含义
同一时间间隔内执行以及调度多个程序
的能力。其实就是CPU的快速切换能力。
进程中的线程是 CPU 负责调度执行,但是 CPU 能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU 会轮询为系统的每个线程服务,由于 CPU 切换的速度很快,给我们的感觉就是这些线程同时执行,这就是并发。
如下图所示,有3个进程,Google Chrome、IntelliJ IDEA、Microsoft Edge,同时这三个进程又有很多线程,这个时候 CPU 就会轮番的处理每一个线程,处理的速度特别快,感觉这些软件的功能都在同时执行,这个就是并发。
并行的含义
- 在同一个时刻上,
同时有多个线程
在被 CPU 调度。
比如这台电脑,内核是4,逻辑处理器是8,是一个双核 CPU,表示 CPU 能同时处理8个线程
多线程到底是如何执行?
并发个并行同时执行的!!!
九、线程的生命周期
什么是线程生命周期?
- 线程从生到死的过程中,经历的各种状态及状态转换。
Java 线程状态
- Java 总共定义了6种状态
- 6种状态定义在 Thread 类内部枚举类中
代码如下:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}