1. 常见方法
方法名 | static | 功能说明 | 注意点 |
---|---|---|---|
start() | 启动一个新线程,在新线程里面运行run方法 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start 方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException | |
run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为 | |
join() | 等待线程运行结束 | ||
join(long n) | 等待线程运行结束,最多等待n毫秒 | ||
getId() | 获取线程长整型的id | id 唯一 | |
getName() | 获取线程名 | ||
setName(String) | 设置线程名 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 修改线程优先级 | Java 中规定线程优先级是1-10的整数,较大的优先级 | |
getState() | 获取线程状态 | Java 中线程状态有6个,分别为:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED | |
isInterrupted() | 判断是否被打断 | 不会清除打断标记 | |
interrupt() | 打断线程 | 如果被打断线程正在 sleep 、wait 、join 会导致被打断的线程抛出InterruptedException,并清除打断标记。如果打断的正在运行的线程,则会设置打断标记。park的线程被打断,也会设置打断标记 | |
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
isAlive() | 线程是否存活 | ||
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程 | |
yield() | static | 提示线程调度器让出当前线程对 CPU 的使用 | 主要是为了测试和调试 |
currentThread() | static | 获取当前正在执行的线程 |
2. 方法演示
2.1 setName 与 getName
2.1.1 默认情况下的线程名
@Slf4j
public class Test01 {
public static void main(String[] args) {
new Thread(() -> log.info(Thread.currentThread().getName())).start();
log.info(Thread.currentThread().getName());
}
}
默认情况下,主线程名称为 main,其他线程名为 Thread-n 的形式,其中 n 为从 0 开始的整型,可以通过 setName 方法进行修改
2.1.2 修改默认线程名
@Slf4j
public class Test02 {
public static void main(String[] args) {
Thread.currentThread().setName("m1");
Thread thread = new Thread(() -> log.info(Thread.currentThread().getName()), "t1");
thread.start();
log.info(Thread.currentThread().getName());
}
}
自定义线程名设置成功
2.2 run 与 start
2.2.1 执行 start 方法
@Slf4j
public class Test03 {
public static void main(String[] args) {
Thread thread = new Thread(() -> log.info(Thread.currentThread().getName()));
thread.start();
}
}
在新线程中执行 run 方法的代码逻辑
2.2.2 执行 run 方法
@Slf4j
public class Test03 {
public static void main(String[] args) {
Thread thread = new Thread(() -> log.info(Thread.currentThread().getName()));
thread.run();
}
}
在主线程中执行 run 方法的代码逻辑,相当于普通方法
2.3 sleep 与 yield
2.3.1 sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
2.3.2 yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
2.3.3 sleep方法的应用 (限制对CPU的使用)
2.3.3.1 不使用 sleep 方法,cpu的使用情况
public class Test04 {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
// try {
// Thread.sleep(50);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
}
}).start();
}
}
.java 源文件未指定 package,并且 CLASSPATH 存在 .:(当前路径),执行以下步骤:
- 将文件 Test04.java 上传到 Linux 服务器
- 执行命令 javac Test04.java (编译 .java 文件)
- 执行命令 java Test04 (运行 Test04 的 main 方法)
如果指定了package,执行以下步骤:
- 将文件 Test04.java 上传到 Linux 服务器
- 执行命令 javac Test04.java -d ./ (编译 .java 文件)
- 执行命令 java 类的全路径.Test04 (运行 Test04 的 main 方法)
通过 top 命令查看运行结果
CPU 占用飙升到 100%
2.3.3.2 不使用 sleep 方法(将相关注释去除),cpu的使用情况
public class Test04 {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
通过 top 命令查看运行结果
使用了 sleep 方法,尽管睡眠时间很短,但是 CPU 占用锐减
2.4 线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
2.4.1 现象演示
@Slf4j
public class Test05 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
log.info(Thread.currentThread().getName() + ":" + i);
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
log.info(Thread.currentThread().getName() + ":" + i);
}
}, "t2");
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
将 t1 的优先级设置到最大值 10,将 t2 的优先级设置到最大值 1
即使 t2 线程的优先级为最小值 1 ,也是有机会优先执行完
2.5 join
2.5.1 执行下列代码,RESULT 的结果是多少?
@Slf4j
public class Test06 {
private static int RESULT = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
// 模拟业务处理逻辑
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(3));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
RESULT = 1;
}, "t1");
t1.start();
log.info("result : " + RESULT);
}
}
运行结果为: -1
2.5.2 使用 sleep 方法行不行,为什么?
使用 sleep 方法不太合适,sleep 时间过长影响效率,sleep 时间过短拿不到正确结果
2.5.3 使用 join 方法
@Slf4j
public class Test06 {
private static int RESULT = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
// 模拟业务处理逻辑
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(3));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
RESULT = 1;
}, "t1");
t1.start();
t1.join();
log.info("result : " + RESULT);
}
}
运行结果为: 1
2.5.3 join 方法耗时分析
2.5.3.1 案例1
@Slf4j
public class Test07 {
private static int R1 = -1;
private static int R2 = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
R1 = 1;
}, "t1");
Thread t2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
R2 = 2;
}, "t2");
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.info("R1: {} R2: {} cost: {}s", R1, R2, (end - start) / 1000);
}
}
一共耗时 2 s,图解如下:
运行到 t1 的 join 方法时,t1 运行结束,t2 已运行 1s,所以还需要等到 1s 就可以运行完毕
2.5.3.2 案例2
将 t1.join() 放入到线程 t2中
@Slf4j
public class Test08 {
private static int R1 = -1;
private static int R2 = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
R1 = 1;
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
R2 = 2;
}, "t2");
long start = System.currentTimeMillis();
t1.start();
t2.start();
t2.join();
long end = System.currentTimeMillis();
log.info("R1: {} R2: {} cost: {}s", R1, R2, (end - start) / 1000);
}
}
一共耗时 3 s,图解如下(假设 t1 优先获得时间片):
t2 线程需要等到 t1 线程执行完毕才可以执行,相当于同步执行
2.5.4 有时间的 join
2.5.4.1 join 时间小于业务处理时间
@Slf4j
public class Test09 {
private static int RESULT = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
// 模拟业务处理逻辑
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
RESULT = 1;
}, "t1");
t1.start();
t1.join(1000);
log.info("result : " + RESULT);
}
}
获取默认值:-1
2.5.4.2 join 时间大于业务处理时间
@Slf4j
public class Test09 {
private static int RESULT = -1;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
// 模拟业务处理逻辑
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
RESULT = 1;
}, "t1");
t1.start();
t1.join(2500);
log.info("result : " + RESULT);
}
}
获取处理后的值:1
2.6 interrupt、isInterrupted、interrupted
2.6.1 打断阻塞线程
sleep,wait,join 方法都会让线程进入阻塞状态,我们以 sleep 为例
@Slf4j
public class Test10 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
// 模拟业务处理逻辑
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
log.info(" 打断状态: {}", t1.isInterrupted());
}
}
运行结果:打断一个正在阻塞的线程,会抛出 InterruptedException,并且 interrupt 方法会清除打断标记
PS:主线程的 sleep 时间和 t1 现成的睡眠差距稍微大点,否则 t1 线程就可能处于非阻塞状态,isInterrupted 方法将会返回 true
2.6.2 打断正常线程
@Slf4j
public class Test11 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if (interrupted) {
log.debug(" 打断状态: {}", interrupted);
break;
}
}
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
}
}
运行结果:打断一个正在运行的的线程,isInterrupted 方法将会返回 true
2.6.3 打断 park 线程
@Slf4j
public class Test12 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
log.info("park...");
LockSupport.park();
log.info("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
}
}
运行结果:打断一个正在 park 的的线程,isInterrupted 方法将会返回 true
如果一个线程的打断状态已经为 true,会发生什么现象?
@Slf4j
public class Test13 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
log.info("park...");
LockSupport.park();
log.info("打断状态:{}", Thread.currentThread().isInterrupted());
}
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
}
}
运行结果:通过打印日志的时间戳,我们可以看出来,如果一个线程的打断标记为 true,则 park 失效
将方法 isInterrupted 改成 interrupted
@Slf4j
public class Test13 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
log.info("park...");
LockSupport.park();
log.info("打断状态:{}", Thread.interrupted());
}
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
}
}
运行结果:t1 线程又被 park 住了
2.6.4 相关方法的应用 -- 两阶段终止模式
2.6.4.1 Two Phase Termination
在一个线程 T1 中如何 “优雅” 终止线程 T2?这里的 “优雅” 指的是给 T2 一个 “料理后事” 的机会。
2.6.4.2 错误思路
- 使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
- 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止
2.6.4.3 优雅停止
2.6.4.3.1 利用 isInterrupted
@Slf4j
public class TwoPhaseTermination01 {
private Thread thread;
public void start() {
thread = new Thread(() -> {
while (true) {
Thread currentThread = Thread.currentThread();
boolean interrupted = currentThread.isInterrupted();
if (interrupted) {
log.info("料理后事");
break;
}
try {
// 睡眠1s
TimeUnit.MILLISECONDS.sleep(1000);
log.info("保存数据");
} catch (InterruptedException e) {
currentThread.interrupt();
}
}
}, "监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
public static void main(String[] args) throws Exception {
TwoPhaseTermination01 twoPhaseTermination01 = new TwoPhaseTermination01();
twoPhaseTermination01.start();
TimeUnit.MILLISECONDS.sleep(3500);
log.info("stop");
twoPhaseTermination01.stop();
}
}
图解流程如下:
2.6.4.3.2 利用停止标记
@Slf4j
public class TwoPhaseTermination02 {
private Thread thread;
// 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性
private volatile boolean stop = false;
public void start() {
thread = new Thread(() -> {
while (true) {
if (stop) {
log.info("料理后事");
break;
}
try {
// 睡眠1s
TimeUnit.MILLISECONDS.sleep(1000);
log.info("保存数据");
} catch (InterruptedException ignore) {
}
}
}, "监控线程");
thread.start();
}
public void stop() {
stop = true;
thread.interrupt();
}
public static void main(String[] args) throws Exception {
TwoPhaseTermination02 twoPhaseTermination02 = new TwoPhaseTermination02();
twoPhaseTermination02.start();
TimeUnit.MILLISECONDS.sleep(3500);
log.info("stop");
twoPhaseTermination02.stop();
}
}
图解流程如下:
2.7 getStatus
各种状态代码演示
@Slf4j
public class Test14 {
public static void main(String[] args) throws Exception {
synchronized (Test14.class) {
Thread t1 = new Thread(() -> log.info(Thread.currentThread().getName()), "t1");
Thread t2 = new Thread(() -> {
while (true) {
}
}, "t2");
Thread t3 = new Thread(() -> {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t3");
Thread t4 = new Thread(() -> {
try {
t2.join();
log.info(Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t4");
Thread t5 = new Thread(() -> {
synchronized (Test14.class) {
log.info(Thread.currentThread().getName());
}
}, "t5");
Thread t6 = new Thread(() -> log.info(Thread.currentThread().getName()), "t6");
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
TimeUnit.MILLISECONDS.sleep(500);
log.info("t1 state {}", t1.getState());
log.info("t2 state {}", t2.getState());
log.info("t3 state {}", t3.getState());
log.info("t4 state {}", t4.getState());
log.info("t5 state {}", t5.getState());
log.info("t6 state {}", t6.getState());
}
}
}
2.8 isAlive
@Slf4j
public class Test15 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(2000);
log.info(Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
log.info("thread isAlive " + t1.isAlive());
t1.join();
log.info("thread isAlive " + t1.isAlive());
}
}
得出结论:线程 t1 未执行完是存活的,当执行完业务逻辑方法返回 false
2.9 守护线程
t1、t2 都是非守护线程
@Slf4j
public class Test16 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
log.info("t1 => " + i);
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 50; i++) {
log.info("t2 => " + i);
}
}, "t2");
t1.start();
t2.start();
}
}
当 t1、t2 都是非守护线程,必须等到 t1、t2 都执行完才会退出 JVM
t1 是非守护线程,t2 是守护线程
@Slf4j
public class Test16 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
log.info("t1 => " + i);
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 50; i++) {
log.info("t2 => " + i);
}
}, "t2");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
t1 是非守护线程, t2 是守护线程,当 t1 执行完 JVM 中不存在非守护线程,即使 t2 未执行完,也会退出 JVM