Thread状态流转、方法使用、原理分析
热衷学习,热衷生活!😄
沉淀、分享、成长,让自己和他人都能有所收获!😄
一、Thread状态
Java
的线程状态描述在Thread
类里面的枚举类State
中,包路径为java.lang.Thread.State
,总共包含以下六种状态:NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
1 | public enum State { |
上面的六种状态相当于描述了一个线程的生命周期,这些状态之间是可以流转的,那么线程的状态是如何流转的呢,我们看下图:
NEW
:未启动状态。1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) {
Thread t = new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 这个时候只是new了一个线程,并没有调用start()方法
// 输出 NEW
System.out.println(t.getState().name());
}RUNNABLE
:可运行状态,处于可运行状态的线程正在Java
虚拟机中运行,但也可能是正在等待来自操作系统资源,比如CPU。在RUNNABLE
状态中包含了RUNNING
、READY
两个状态,这两个状态是可以互相流转的。当线程调用start()
方法后,线程就处于READY
状态,等待操作系统分配CPU
时间片,分配后进入RUNNING
状态。当调用yield()
方法后,只是谦让的允许当前线程让出CPU
,但是不一定让,由操作系统决定,如果让了当前线程就会进入READY
状态,等待系统分配CPU
时间片再次进入RUNNING
状态。1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) {
Thread t = new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
t.start();
// 调用start()方法
// 输出RUNNABLE
System.out.println(t.getState().name());
}BLOCKED
:阻塞状态。当发生线程锁竞争状态下,没有获取到锁的线程会被挂起进入阻塞状态,比如synchronized
锁。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public static void main(String[] args) throws InterruptedException {
final Object lock = new Object();
Thread t = new Thread(){
public void run() {
synchronized (lock){
System.out.println(Thread.currentThread().getName());
}
}
};
// main线程先获取锁, t线程start的时候进入阻塞状态
synchronized (lock) {
t.start();
Thread.sleep(1000);
// 输出BLOCKED
System.out.println(t.getState().name());
}
}WIITING
:等待状态,可被唤醒的等待状态,此时线程不会执行也不会被调度,:可被唤醒的等待状态,此时线程不会被执行也不会被系统调度,此状态可以通过 synchronized 获得锁,调用 wait 方法进入等待状态,最后通过Object.notify()
、Object.nofifyAll()
唤醒。下列方法可以让线程进入WAITING
状态:Object.wait()
、Thread.join()
、LockSupport.park()
,调用以下方法可以唤醒登台的线程进入RUNNABLE
状态:Object.notify()
、Object.nofifyAll()
、LockSupport.unpark(Thread)
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public static void main(String[] args) throws Exception {
final Object lock = new Object();
Thread t = new Thread(){
public void run(){
try {
synchronized (lock) {
lock.wait();
System.out.println(Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
Thread.sleep(1000);
synchronized (lock) {
// 输出WAITING
System.out.println(t.getState().name());
lock.notify();
}
}TIMED_WAITING
:具有等待时间的等待状态,此时线程不会执行也不会被调度,直到等待时间到期后才会被执行。下列方法可以让线程进入TIMED_WAITING
状态:Thread.sleep(long)
、Object.wait(long)
、Thread.join(long)
、LockSupport.parkNanos()
、LockSupport.parkUntil()
。除了等待时间到期会被执行外,还可以调用以下方法:Object.notify()
、Object.nofifyAll()
、LockSupport.unpark(Thread)
。1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) throws Exception {
Thread t = new Thread(() ->{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
Thread.sleep(1000);
// 输出TIMED_WAITING
System.out.println(t.getState().name());
}TERMINATED
:已终止状态,线程已完成执行或者发生了异常终止。1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) {
Thread t = new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
t.start();
// 输出 TERMINATED
System.out.println(t.getState().name());
}
二、创建Thread方式
创建线程的方式有三种,分别是:继承Thread
类、实现Runnable
接口、实现Callable
接口和使用Future
。
继承Thread
1 | public class MyThread extends Thread { |
实现Runnable
1 | public class MyThread implements Runnable { |
实现Callable接口和使用Future
1 | public class ThreadDemo implements Callable { |
三、Thread方法使用
Thread
类常用的方法有以下:
start()
:启动当前线程。run()
:线程启动的时候执行的方法。currentThread()
:静态方法,返回执行当前代码的线程。getName()
:获取当前线程的名字。setName(String name)
:设置当前线程的名字yield()
:释放当前CPU的执行权(但也有可能下一刻的执行权又回到了当前线程,主控权还是在CPU手上)。join()
:当线程a调用线程b的join()
方法,此时线程a进入阻塞状态,直到线程b完全执行完之后,线程a结束阻塞状态。stop
:当执行此方法时,强制结束当前线程(已停用)。sleep()
:让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前进程是阻塞状态。getPriority()
:获取线程优先级。isAlive()
:判断当前线程是否存活(线程执行完之前都是存活的)。setDeamon()
:设置守护线程。isDaemon()
:是否是守护线程。isInterrupted()
:线程是否中断。
守护线程
守护线程又称后台线程,默认创建出来的线程都是前台线程,可以通过setDeamon(true)
设定为后台线程。
后台线程和前台线程主要区分在结束时机:当一个进程中的所有前台线程都执行完毕,无论进程中的后台线程是否执行完毕,都要强制中断。
看以下例子:
1 | public static void main(String[] args) { |
上面代码输出如下:
1 | Jack:You jump,I jump |
从输出可以看出前台线程执行完毕之后,后台线程就立即停止了,如果把while(true)
去掉注释,那么后台线程会一直执行。
sleep
sleep()
方法可以使当前方法的线程阻塞指定毫秒,超时后,该线程方法会自动回到RUNNABLE
状态等待再次分配时间片执行。
1 | public static void main(String[] args) { |
join
join()
方法会阻塞调用当前方法的线程,并在join()
方法所属对象线程上等待,直到该线程执行完毕才会解除阻塞继续执行。
举个栗子:a线程调用b线程的join()
方法,那么a线程就会进入阻塞状态,直到b线程执行完毕才会解除阻塞继续往下执行。
1 | public static void main(String[] args) { |
上面代码输出如下:
1 | show:开始显示图片 |
wait & nofify
wait()
和notify()
方法是在Object
类定义的,用来协调线程同步使用,相比join()
同步的及时性更强,因为join()
方法必须等待另外一个线程的所有工作都结束。
1 | public static boolean isFinish; |
上面输出如下:
1 | show:开始显示图片 |
从上面输出我们看出当下载线程图片下载完毕之后,显示图片线程就执行了,不需要等待附件下载完毕再执行。
Oject
还有一个notifyAll()
方法,当该方法被调用时,等待队列中的所有对象都被会唤醒。
yield
yield()
方法会使线程让出CPU
,但不是暂停执行,线程还会继续进行CPU
的争夺,但是也不是一定就会让出,这个由操作系统决定。如果一个线程不那么重要,或者对应任务的优先级非常低,或者不希望它占用过多的资源,那么就可以使用yield()
方法给予其它线程更多的执行机会。
sleep和wait的区别
- 被
wait()
方法阻塞的线程可以通过notify()
或者notifyAll()
方法唤醒,而执行sleep(0
方法的线程只能一直休眠到指定时间,不可被唤醒。 - 当被
wait()
方法阻塞的线程被唤醒的时候,会释放目标对象的锁,而sleep()
方法指定的睡眠时候到的时候不会释放任何资源。