一、过时方法
一些不推荐使用的方法已经过时,容易破坏同步代码块,使对象的锁得不到释放,进而造成线程死锁
二、守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
正常情况下:主线程运行结束,但t1线程仍未结束,因此进程并不会结束(只要有一个线程还在运行,Java进程并不会结束)
将t1设置为守护线程后:
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
// 将t1设置为守护线程(默认值为非守护线程:false)
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}
运行结果:主线程(非守护)运行结束后,无论守护线程(t1)是否执行完毕均会强制结束
守护线程应用:
● 垃圾回收器线程就是一种守护线程【再堆内存中分配的对象,当没有其它对象引用这些对象时,这些未被引用的对象就会定期被垃圾回收
】
● Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
三、线程状态(五种)
五种状态,这是从 操作系统 层面来描述的
● 初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联(eg:Java中newThread对象,但未调用其start()方法
)
● 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行,但暂时还未获得CPU的时间片
● 【运行状态】指获取了 CPU 时间片运行中的状态
—— 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,其切换会导致线程的上下文切换
● 【阻塞状态】
—— 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU(调度器不会考虑调度阻塞状态的线程),会导致线程上下文切换,进入【阻塞状态】
—— 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
—— 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
● 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
四、线程状态(六种)
六种状态,是从Java API层面来描述的
根据Thread.State枚举,分为六种状态
● NEW
线程刚被创建,但是还没有调用 start() 方法
● RUNNABLE
当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)操作系统层面的阻塞状态在Java中还是RUNNABLE
● BLOCKED
, WAITING
, TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
● TERMINATED
当线程代码运行结束
3.1 六种状态演示
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
// ① 并未调用线程1的start()方法===>NEW
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
// ② 不断运行===>RUNNABLE(既有可能分到时间片也有可能为分到时间片,也有可能陷入操作系统的IO阻塞)
Thread t2 = new Thread("t2") {
@Override
public void run() {
while(true) { // runnable
}
}
};
t2.start();
// ③ 打印后执行完毕===>TERMINATED
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
// ④ 线程处于阻塞状态【sleep时间足够长===>TIMED_WAITING(有时限的的等待)】
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
// ⑤ 等待t2(死循环)一直运行结束===>WAITING(没有时限的等待)
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
// ⑥ t4线程会先对对象加锁,t6线程不能获取对象的锁===>t6陷入BLOCKED
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state {}", t1.getState());
log.debug("t2 state {}", t2.getState());
log.debug("t3 state {}", t3.getState());
log.debug("t4 state {}", t4.getState());
log.debug("t5 state {}", t5.getState());
log.debug("t6 state {}", t6.getState());
System.in.read();
}
}
运行结果:
四、多线程应用统筹分析
如何使用多线程得到最优方案?
后两种办法均耗费时间较长
上图可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。因此合理分配便可节约大量时间,我们可以使用多线程的思想便可找到最优的方案
方案实现:
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test16")
public class Test16 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("洗水壶");
sleep(1);
log.debug("烧开水");
sleep(5);
},"老王");
Thread t2 = new Thread(() -> {
log.debug("洗茶壶");
sleep(1);
log.debug("洗茶杯");
sleep(2);
log.debug("拿茶叶");
sleep(1);
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("泡茶");
},"小王");
t1.start();
t2.start();
}
}
运行结果: