java线程常见方法
1.start与run
直接调用run,主线程会运行所有代码
@Slf4j
public class StartAndRunTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("running...");
});
//输出NEW状态
System.out.println(thread.getState());
thread.run();
//还是输出NEW状态
System.out.println(thread.getState());
log.info("主线程");
//输出
//NEW
//2024-05-02 20:27:38,181 INFO [main] c.w.j.m.StartAndRunTest.lambda$main$0(13): running...
//NEW
//2024-05-02 20:27:38,186 INFO [main] c.w.j.m.StartAndRunTest.main(21): 主线程
}
}
使用start调用,各线程执行各线程的代码
@Slf4j
public class StartAndRunTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("running...");
});
//输出NEW状态
System.out.println(thread.getState());
thread.start();
//输出RUNNABLE状态
System.out.println(thread.getState());
log.info("主线程");
//输出
//NEW
//2024-05-02 20:27:38,181 INFO [main] c.w.j.m.StartAndRunTest.lambda$main$0(13): running...
//NEW
//2024-05-02 20:27:38,186 INFO [main] c.w.j.m.StartAndRunTest.main(21): 主线程
}
}
总结
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
2.sleep与yield方法
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
sleep和yield的区别在于:
相同点:都是让出当前cpu的时间片。
不同点:sleep之后程序会处于阻塞状态,cpu不会考虑把时间片再分给他,直到其睡醒。而yield让出时间片之后,cpu还是会考虑调用此线程。
具体区别:https://blog.csdn.net/u013470512/article/details/21049009?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171465440816800182775897%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171465440816800182775897&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-21049009-null-null.142v100pc_search_result_base9&utm_term=sleep%E5%92%8Cyield%E7%9A%84%E5%BC%82%E5%90%8C&spm=1018.2226.3001.4187
sleep的应用
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让cpu的使用权给其他程序。
也就是说,下面代码在没有sleep时运行的话,cpu会飙到99%,但加了sleep之后就会恢复正常。
while (true){
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 可以用wait或条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景
3. 线程优先级 setPriority(int)
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
4. join方法
等待某线程运行结束
以调用方角度来讲,如果
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
/**
* 若注释掉t1.join();则最终打印 r 的值是0
* 若加上t1.join();则最终打印 r 的值是10
* @author Spider Man
* @date 2024-05-09 10:14
*/
@Slf4j
public class JoinTest {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("结束");
r=10;
},"t1");
t1.start();
t1.join();
log.debug("结束,r结果为:{}",r);
}
}
5. interrupt 方法
可以理解为给要打断的线程发送 打断信号,发送这个信号之后只是表示需要打断(即需要被打断的线程的打断标记isInterrupted 由false变为true),线程并不会自动终止。
- 打断阻塞状态的线程
sleep、wait、join的线程,会清空打断状态,抛出打断异常 java.lang.InterruptedException: sleep interrupted,并把打断状态重置为false
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("interrupt...");
t1.interrupt();
TimeUnit.SECONDS.sleep(1);
log.debug("打断标记为...{}", t1.isInterrupted());
}
- 打断正常运行的线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
//对于正常运行的线程(非阻塞线程),如果收到打断信号,则isInterrupted()返回true,并抛出异常
if (Thread.currentThread().isInterrupted()) {
log.debug("收到打断信号,线程终止");
break;
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("interrupt.....");
t1.interrupt();
}
两阶段终止 模式
问题:在一个线程T1中如何优雅终止线程T2?这里的优雅指的是给T2一个料理后事的机会。
错误思路
- 使用线程对象的stop() (已被废弃)方法停止线程
stop方法会正在杀死线程,如何这时线程对象锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁 - 使用System.exit(int)方法停止线程
这种做法会让整个程序停止,我们仅是停止一个线程。
@Slf4j
public class Interrupt3Test {
private Thread monitor;
public static void main(String[] args) throws InterruptedException {
Interrupt3Test interrupt3Test = new Interrupt3Test();
interrupt3Test.start();
TimeUnit.SECONDS.sleep(5);
interrupt3Test.stop();
}
public void start() {
monitor = new Thread(() -> {
while (true){
Thread thread = Thread.currentThread();
if (thread.isInterrupted()){
log.debug("料理后事");
break;
}
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
//此处是关键点,因为线程在sleep时,会清除中断标志位,所以需要重新设置标志位
// 捕获异常后,需要将标志位重新设置
thread.interrupt();
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
不推荐使用的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
守护线程 setDaemon(boolean)
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
@Slf4j
public class daemonTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
}
},"t1");
//如果没有设置守护线程,主线程结束,t1线程不会结束
t1.setDaemon(true);
t1.start();
TimeUnit.SECONDS.sleep(3);
log.debug("main 结束");
}
}
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等
待它们处理完当前请求
线程的五种状态
从 操作系统 层面来描述的
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换 - 【阻塞状态】
1、 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入
【阻塞状态】
2、 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
3、 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑
调度它们 - 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
线程的六种状态
这是从 Java API 层面来描述的,根据 Thread.State 枚举,分为六种状态。
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
NEW
线程刚被创建,但是还没有调用 start() 方法
-RUNNABLE
当调用了start()
方法之后,注意,Java API 层面的RUNNABLE
状态涵盖了 操作系统 层面的
【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为
是可运行)BLOCKED
,WAITING
,TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节
详述TERMINATED
当线程代码运行结束
更详细的会在锁后面详细介绍
@Slf4j
public class ThreadStatusTest {
public static void main(String[] args) {
//不调用start NEW
Thread t1 = new Thread(() -> {
}, "t1");
//RUNNABLE
Thread t2 = new Thread(() -> {
while (true) {
}
}, "t2");
//WAITING
Thread t3 = new Thread(() -> {
synchronized (ThreadStatusTest.class) {
try {
TimeUnit.SECONDS.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t3");
//TIMED_WAITING
Thread t4 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t4");
//BLOCKED
Thread t5 = new Thread(() -> {
synchronized (ThreadStatusTest.class) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t5");
//TERMINATED
Thread t6 = new Thread(() -> {
}, "t6");
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
log.debug("{}", t1.getState());
log.debug("{}", t2.getState());
log.debug("{}", t3.getState());
log.debug("{}", t4.getState());
log.debug("{}", t5.getState());
log.debug("{}", t6.getState());
}
}