线程的调度&&线程的生命周期&&Daemon线程
线程简介
什么是线程
线程是现代操作系统调度的最小单元,也叫做轻量级的进程。
在一个进程里面可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且可以访问共享的内存变量。
线程与进程
在引入线程的操作系统中,通常是把进程作为资源分配的基本单位。把线程作为独立运行和独立调度的基本单位。
线程与进程的区别:
- 进程间相互独立,同一进程的各个线程共享。某进程内的线程对于其他进程不可见。
进程间通过IPC通信,线程间可以直接读取进程的内存共享变量进程通信。
线程上下文切换比进程上下文切换要快的多。
线程的调度
线程调度模型
在计算机中,线程的调度有2中模型,分别是分时调度模型、抢占式调度模型。
分时调度模型:
分时调度模型是指让所有的线程轮流获得CPU的使用去权,并且平均分配每个线程占用CPU的时间片。
抢占式调度模型:
抢占式调度模式是指让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个占用CPU,当它失去CPU的使用权后,在随机选择其他线程获取CPU使用权。
Java虚拟机默认采用抢占式调度模式。
线程的优先级
线程的优先级用1~10表示。数字越大优先级越高。在Thread
的类中提供对线程设置优先级的方法,同时还定义了3个静态常量。如下:
setPriority(int priority):设置线程优先级
MAX_PRIORITY:优先级最高级别,相当于10。
MIN_PRIORITY:优先级最低级别,相当于1
NORM_PRIORITY:默认优先级,相当于5
注意:虽然Java提供了线程优先级的设置,但是不同的操作系统对线程的优先级支持是不一样的,不能很好的和Java中的线程一一对应。所以线程的优先级用来作为程序正确性的依赖。
线程休眠
public static native void sleep(long mills) throws InterruptedException;
如果我们想让线程休眠,我们可以在当前线程调用sleep(long mills)
方法,这个方法会抛出一个InterruptedException
异常。
需要注意的是,sleep(long mills)
是一个静态方法,只能控制当前正在运行的线程休眠,不能控制其他线程休眠,在休眠结束后,线程就会返回到就绪状态,而不是立即开始运行状态。
线程让步
public static native void yield();
线程让步可以通过yield()
方法来实现,该方法和sleep()
方法有些相似,都可以让当前正在运行的线程暂停,区别在于yield()
方法不会阻塞该线程,只是将线程转换成就绪状态,让系统的调度器重新调度一次。
线程插队
public final void join() throws InterruptedException{
this.join(0L);
}
public final synchronized void join(long var1) throws InterruptedException {
long var3 = System.currentTimeMillis();
long var5 = 0L;
if(var1 < 0L) {
throw new IllegalArgumentException("timeout value is negative");
} else {
if(var1 == 0L) {
while(this.isAlive()) {
this.wait(0L);
}
} else {
while(this.isAlive()) {
long var7 = var1 - var5;
if(var7 <= 0L) {
break;
}
this.wait(var7);
var5 = System.currentTimeMillis() - var3;
}
}
}
}
在Thread
类中提供了一个join()
方法来实现线程插队的功能。当某个线程中调用其他线程的join()时,调用的线程将阻塞,直到被join()
加入的线程执行完成后才会继续运行。
线程的生命周期及状态
线程的整个生命周期可以分为5个阶段,分别是新建(创建)状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead)。
新建状态(New):创建线程对象后,该线程对象就处于新建状态了。
就绪状态(Runnable):当线程调用了start()方法后,它就进入了就绪状态。此时它只是具备了运行条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。
运行状态(Running):处于就绪状态的线程获取CPU的使用权,开始执行
run()
方法,则该线程处于运行状态。当使用完CPU的使用权(时间片)时,系统会剥夺该线程占用的CPU资源,让其他线程获得机会。这时失去CPU的使用权的线程由运行状态变为就绪状态。阻塞状态(Blocked):一个正在运行的线程,在某些特殊情况下(下面列举),会放弃CPU的使用权,进入阻塞状态。线程进入阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
下面列举下线程由运行状态转换到阻塞状态的原因。以及如何从阻塞状态转为就绪状态:
当线程试图获取某个对象的同步锁时,如果该锁被其他线程锁持有,则当前线程就会进入阻塞状态,如果想从阻塞状态进入就绪状态必须获取到其他线程所持有的锁。
当线程调用了一个阻塞式的IO方法时,该线程就会进入阻塞状态,如果想进入就绪状态就必须要等到这个阻塞的IO方法返回。
当线程调用
wait()
方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()
方法唤醒该线程。- 当线程调用了Thread的
sleep()
方法时,也会进入阻塞状态,当时间设置休眠的时间到了,该线程就会进入就绪状态。 - 当在一个线程中调用了另外一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态。
注意:线程进入阻塞状态后,需要进入就绪状态,等待排队,才能到运行状态。
- 死亡状态(Dead):当线程的
run()
方法正常执行完,或者线程抛出未捕获的异常或错误,线程进入死亡状态。一旦进入死亡状态,线程将不再有运行的资格。
线程的生命周期图:
守护线程(Daemon线程)
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。当一个Java虚拟机中不存在飞Daemon的时候,Java虚拟机将会退出。
public final void setDaemon(boolean var1) {
this.checkAccess();
if(this.isAlive()) {
throw new IllegalThreadStateException();
} else {
this.daemon = var1;
}
}
注意:Daemon属性需要在启动之前设置,不能再启动之后设置。
在start()方法之后,设置setDaemon()会报错,如:
at java.lang.Thread.setDaemon(Thread.java:1356)
at com.example.DaemonDemo.main(DaemonDemo.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑,因为Java虚拟机退出时,Daemon线程中的finally块不一定执行。看下面例子:
public class DaemonDemo {
public static void main(String[] args) {
ThreadA thread1 = new ThreadA();
thread1.setName("thread1");
thread1.setDaemon(true);
thread1.start();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + ": 执行结束");
}
}
public static class ThreadA extends Thread {
@Override
public void run() {
System.out.println(getName() + ": 开始执行run()方法");
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(getName() + ": 执行结束");
}
}
}
}
最终结果打印:
thread1: 开始执行run()方法
main: 执行结束
在main线程结束后,Java虚拟机退出了,Daemon线程的finally语句没有执行。