线程的调度模型/线程的优先级
线程的调度模型
目前主要分为两种:分时调度模型和抢占式调度模型;
分时调度模型 系统平均分配CPU的时间片,所有线程轮流占用CPU,即在时间片调度的分配上所有线程“人人平等”;
抢占式调度模型
系统按照线程优先级分配CPU时间片。优先级高的线程优先分配CPU时间片,如果所有就绪线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。由于目前大部分操作系统都是使用抢占式调度模型进行线程调度,Java的线程管理和调度是委托给操作系统完成的,与之相对应,Jmatchava的线程调度也是使用抢占式调度模型,因此Java的线程都有优先级;
线程的优先级
Thread实例的priority属性默认是级别5,对应的类常量是NORM_PRIORITY,优先级最大值为10,最小值为1;Java中使用抢占式调度模型进行线程调度,priority实例属性的优先级越高,线程获得CPU时间片的机会就越多,但也不是绝对的;
线程相关操作方法
sleep的作用是让目前正在执行的线程休眠,让CPU去执行其他的任务。从线程状态来说,就是从执行状态变成限时阻塞状态。Sleep()方法定义在Thread类中,是一组静态方法。当线程睡眠时间满后,线程不一定会立即得到执行,因为此时CPU可能正在执行其他的任务,线程首先进入就绪状态,等待分配CPU时间片以便有机会执行;
Thread的interrupt()方法,此方法本质不是用来中断一个线程,而是将线程设置为中断状态。当我们调用线程的interrupt()方法时,它有两个作用:
(1)如果此线程处于阻塞状态(如调用了Object.wait()方法),就会立马退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做一定的处理,然后让线程退出。更确切地说,如果线程被Object.wait()、Thread.join()和Thread.sleep()三种方法之一阻塞,此时调用该线程的interrupt()方法,该线程将抛出一个InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早终结被阻塞状态。
(2)如果此线程正处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以,程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作。如果线程的interrupt()方法先被调用,然后线程开始调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。如果线程捕获InterruptedException异常后,继续调用阻塞方法,将不再触发;
线程合并:假设有两个线程A和B。现在线程A在执行过程中对另一个线程B的执行有依赖,具体的依赖为:线程A需要将线程B的执行流程合并到自己的执行流程中(至少表面如此),这就是线程合并,被动方线程B可以叫作被合并线程;
(1)执行没有时限(timeout)参数的thread.join()调用:在线程合并场景中,若线程A调用B.join()去合入B线程,则在B执行期间线程A处于WAITING状态,一直等线程B执行完成。
(2)执行没有时限(timeout)参数的object.wait()调用:指一个拥有object对象锁的线程,进入相应的代码临界区后,调用相应的object的wait()方法去等待其“对象锁”(Object Monitor)上的信号,若“对象锁”上没有信号,则当前线程处于WAITING状态;
线程的yield(让步)操作的作用是让目前正在执行的线程放弃当前的执行,让出CPU的执行权限,使得CPU去执行其他的线程。处于让步状态的JVM层面的线程状态仍然是RUNNABLE状态,但是该线程所对应的操作系统层面的线程从状态上来说会从执行状态变成就绪状态。线程在yield时,线程放弃和重占CPU的时间是不确定的,可能是刚刚放弃CPU,马上又获得CPU执行权限,重新开始执行。yield()方法是Thread类提供的一个静态方法,它可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是让线程转入就绪状态。yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次;
public class DaemonDemo{
public static final int SLEEP_GAP = 500; //每一轮的睡眠时长
public static final int MAX_TURN = 4; //用户线程执行轮次
//守护线程实现类
static class DaemonThread extends Thread{
public DaemonThread(){
super("daemonThread");
}
public void run(){
Print.synTco("--daemon线程开始.");
for (int i = 1; ; i++) //死循环{
Print.synTco("--轮次:" + i);
Print.synTco("--守护状态为:" + isDaemon());
// 线程睡眠一会,500毫秒
sleepMilliSeconds(SLEEP_GAP);
}
}
}
public static void main(String args[]) throws InterruptedException{
Thread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
//创建一条用户线程,执行4轮
Thread userThread = new Thread(() ->{
Print.synTco(">>用户线程开始.");
for (int i = 1; i <= MAX_TURN; i++){
Print.synTco(">>轮次:" + i);
Print.synTco(">>守护状态为:" + getCurThread().isDaemon());
sleepMilliSeconds(SLEEP_GAP);
}
Print.synTco(">>用户线程结束.");
}, "userThread");
//启动用户线程
userThread.start();
Print.synTco(" 守护状态为:" + getCurThread().isDaemon());
Print.synTco(" 运行结束.");
}
}
Thread.interrupt()方法: 作用是中断线程。将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程;
如果线程被Object.wait、Thread.join、Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait()、sleep()、join()时,才马上会抛出 InterruptedException;
测试当前线程是否已经中断(静态方法),如果连续调用该方法,则第二次调用将返回false。在api文档中说明interrupted()方法具有清除状态的功能,执行后具有将状态标识清除为false的功能;
内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态;测试当前线程是否已经中断(静态方法),返回的是上一次的中断状态,并且会清除该状态,所以连续调用两次,第一次返回true,第二次返回false;
测试线程是否已经中断,但是不能清除状态标识;isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态测试线程当前是否已经中断,但是不能清除状态标识;