1 线程状态
线程从创建到最终的消亡整个生命周期要经的状态:
创建(new)、就绪(runnable)、运行(running)、阻塞(blocked、time waiting、waiting)、消亡(dead)
1)创建(new): 当需要新起一个线程来执行某个子任务时,就创建了一个线程。
2)就绪(runnable): 线程创建后不会立即进入就绪状态,只有线程运行需要的所有条件满足了,才进入就绪状态(比如内存资源,了解JVM内存区域划分的都 知道线程私有程序计数器、Java栈、本地方法栈等内存区域)
3)运行(running):进入就绪状态后要等待获取CPU时间(CPU可能在忙别的事情),当得到CPU执行时间之后,线程便真正进入运行状态。
4)阻塞 :线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程sleep睡眠(睡眠一定的时间之后再重新执行)、用 户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻 塞)。
5)消亡(dead):发生中断或者子任务执行完毕,线程就会被消亡。
状态转换 如下图所示:
2 上下文切换
单核CPU同一时刻只能运行一个线程,当在运行一个线程的过程中转而运行另外一个线程就叫做线程上下文切换(对于进程的上下文切换也是类似的概念);
线程上下文切换过程中需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态继续运行,需要记录的数据:
1.程序计数器(上次执行到哪条指令)
2.CPU寄存器(上次的运行状态对应的变量值)
总之,线程的上下文切换本质上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
线程可以使得任务执行的效率得到提升,但是也会带来一定程度的系统资源的开销。
3 Thread类中的方法
java.lang.Thread类的源码
Thread类实现了Runnable接口,还有自己的关键属性,比如name(线程名称)、priority(优先级1-10,默认为5)、daemon(守护线程标志)、target(执行任务)。
3.1 Thread类常用方法
1)start方法:启动线程,调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,同时,会为线程分配资源。
2)run方法:用户不需要调用,当start方法启动线程并获得CPU执行时间后,便自动执行run方法体中的任务。(所以,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务)。
3)sleep方法:交出CPU让线程睡眠,CPU可执行其他任务。
有两个重载版本
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
注意,sleep方法不释放对象锁,并且调用sleep方法时,必须捕获InterruptedException异常或者向上抛出将该异。当线程睡眠时间满后,不一定会立即得到执行,因为CPU可能正在执行其他任务。所以说调用sleep方法相当于让线程进入阻塞状态。
4)yield方法:该方法会让当前线程交出CPU权限并去执行其他的线程。类似于sleep不会释放锁。但是yield不能控制具体的交出CPU的时间,只能让相同优先级的线程获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
5)join方法
join方法有三个重载版本:
join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。
如果调用的是无参join方法,则等待thread执行完毕;
如果调用的是指定了时间参数的join方法,则等待一定的时间。
实际上,调用join方法是调用了Object的wait方法,wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限,所以join方法同样会让线程释放对一个对象持有的锁。
6)interrupt方法:中断;
一、可以中断一个正处于阻塞状态的线程;
二、通过组合interrupt方法和isInterrupted()方法可停止正在运行的线程。
public class Test {
public static void main(String[]args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("进入睡眠状态");
Thread.currentThread().sleep(10000);
System.out.println("睡眠完毕");
} catch (InterruptedException e) {
System.out.println("中断异常");
}
System.out.println("执行完毕");
}
}
}
执行结果:
进入睡眠状态
中断异常
执行完毕
可知,通过interrupt方法可以中断处于阻塞状态的线程。
但是,直接调用interrupt方法不能中断正在运行中的线程,如果配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。
public class Test {
public static void main(String[]args) throws IOException {
Testtest = new Test();
MyThreadthread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted()&& i<Integer.MAX_VALUE){
System.out.println(i+"while循环");
i++;
}
}
}
}
运行会发现,打印若干个值之后,while循环就停止打印了。
但是不建议这种方式中断线程,可在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值来决定是否执行任务。
class MyThread extends Thread{
private volatile boolean isStop = false;
@Override
public void run() {
int i = 0;
while(!isStop){
i++;
}
}
public void setStop(boolean stop){
this.isStop= stop;
}
}
7)stop方法:不安全,已废弃。因为调用stop方法会直接终止run方法,并抛出ThreadDeath错误,会完全释放锁,导致对象状态不一致。此方法基本不用。
8)destroy方法:已废弃,一般不用。
3.2 Thread类关于线程属性的方法
1)getId:用来得到线程ID
2)getName和setName:获取或设置线程名称。
3)getPriority和setPriority:获取和设置线程优先级。
4)setDaemon和isDaemon:设置和判断线程是否为守护线程。
3.3 线程通过方法调用进行的状态转换
Thread类中的方法调用会引起线程状态发生变化,如下图:
4 守护线程和用户线程的区别
守护线程依赖于创建它的线程,而用户线程则不依赖。
举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡;但是用户线程会一直运行完毕,不随main线程结束而结束。在JVM中,像垃圾收集器线程就依赖于运行的线程,是守护线程。
静态方法Thread.currentThread()用来获取当前线程。