一、Thread.start()方法
start()方法:启动子线程
new Thread();当前线程的状态为NEW
调用start()方法之后当前线程的状态变为RUNNABLE
二、Thread.sleep()静态方法
1. 调用sleep会让当前线程从Running进入Timed Waiting 状态
2.其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
3. 睡眠结束后的线程未必会立刻得到执行
4. 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性。
总结:在没有利用CPU来计算时,不要让while(true)空转cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序
- 可以使用wait或条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景
三、yield()
1. 调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其他线程
2. 具体的实现依赖于操作系统的任务调度器。
四、线程优先级setPriority
- 线程优先级会提示调度器优先调度该线程,但它仅仅是提示,调度器可以忽略它
- 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用。
五、join() 等待线程结束
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
六、interrupt()方法
interrupt打断sleep,wait,join的线程。叫醒线程。
七、两阶段终止模式
如何终止一个线程
终止一个线程早期Thread是提供对应的停止方法的,比如stop等,但是到后来停止一个线程的api都已经过时了,即存在风险,不推荐使用,那如果碰到这种线程停止的需求如何实现, 且是优雅的实现, 那么两个阶段终止模式就可以实现。它的意思就是, 第一阶段时准备停止阶段,第二阶段是停止阶段。思路就是 B线提供一个一个标志,如果别的线程把这个标志设计为true,那么当前线程就终止。
class TwoPhaseTermination{
Logger logger =LoggerFactory.getLogger(TwoPhaseTermination.class);
private Thread monitor;
//启动监控线程
public void start(){
monitor = new Thread(()->{
while (true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
logger.info("料理后事");
break;
}
try {
Thread.sleep(1000);
logger.info("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置打断标记
monitor.interrupt();
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
八、不推荐使用方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 回复线程运行 |
九、主线程与守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
public void testDaemon() throws InterruptedException {
logger.info("开始运行...");
Thread t1 = new Thread(() -> {
logger.info("开始运行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("运行结束...");
},"daemon");
//设置该线程为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
logger.info("运行结束...");
}
- 垃圾回收器线程就是一种守护线程
- Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待他们处理完当前请求。
十、线程的五种状态
从操作系统层面来描述,线程有五种状态
【初始状态】:仅是在语言层面创建了线程对象,还未与操作系统线程关联
【可运行状态】:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行
【运行状态】:指获取了CPU时间片运行中的状态
- 当CPU时间片用完,会从【运行状态】转换至【可运行状态】 ,会导致线程的上下文切换。
【阻塞状态】:
- 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
- 等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说,只要他们一直不唤醒,调度器就一直不会考虑调度他们。
【终止状态】:表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态。
十一、线程的六种状态
六种状态是从Java API层面来描述,根据Thread.state枚举,分为六种状态。
【NEW】:线程刚被创建,但是还没有调用start()方法
【RUNNABLE】:当调用了start()方法之后,注意Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO导致的线程阻塞,在java里无法区分,仍然认为是可运行)
【BLOCKED、WAITING、TIMED_WAITING】三种状态都是java api层面对【阻塞状态】的戏份,后面会在状态转换一节详细讲述。
【TERMINATED】:当线程代码运行结束
public void testThreadState() throws InterruptedException {
Thread t1 = new Thread("t1"){
@Override
public void run(){
logger.info("running...");
}
};
Thread t2 = new Thread("t2"){
@Override
public void run(){
while (true){
}
}
};
t2.start();
Thread t3 = new Thread("t3"){
@Override
public void run(){
logger.info("running...");
}
};
t3.start();
Thread t4 = new Thread("t4"){
@Override
public void run(){
synchronized (ThreadTest.class){
try {
Thread.sleep(100000);
}catch (Exception e){
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5"){
@Override
public void run(){
try {
t2.join();
}catch (Exception e){
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6"){
@Override
public void run(){
synchronized (ThreadTest.class){
try {
Thread.sleep(100000);
}catch (Exception e){
e.printStackTrace();
}
}
}
};
t6.start();
Thread.sleep(500);
logger.info("t1的状态{}",t1.getState()); //NEW
logger.info("t2的状态{}",t2.getState()); //RUNNABLE
logger.info("t3的状态{}",t3.getState()); //TERMINATED
logger.info("t4的状态{}",t4.getState()); //TIMED_WAITING
logger.info("t5的状态{}",t5.getState()); //WAITING
logger.info("t6的状态{}",t6.getState()); //BLOCKED
}