线程的调度&&线程的生命周期&&Daemon线程

来源:互联网 发布:java page cache 编辑:程序博客网 时间:2024/06/05 06:04

线程的调度&&线程的生命周期&&Daemon线程

线程简介

什么是线程

线程是现代操作系统调度的最小单元,也叫做轻量级的进程

在一个进程里面可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且可以访问共享的内存变量

线程与进程

在引入线程的操作系统中,通常是把进程作为资源分配的基本单位。把线程作为独立运行和独立调度的基本单位

线程与进程的区别:

  • 进程间相互独立,同一进程的各个线程共享。某进程内的线程对于其他进程不可见。
  • 进程间通过IPC通信,线程间可以直接读取进程的内存共享变量进程通信。

  • 线程上下文切换比进程上下文切换要快的多。


线程的调度

线程调度模型

在计算机中,线程的调度有2中模型,分别是分时调度模型、抢占式调度模型。

  • 分时调度模型:

    分时调度模型是指让所有的线程轮流获得CPU的使用去权,并且平均分配每个线程占用CPU的时间片。

  • 抢占式调度模型:

    抢占式调度模式是指让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个占用CPU,当它失去CPU的使用权后,在随机选择其他线程获取CPU使用权。

Java虚拟机默认采用抢占式调度模式

线程的优先级

线程的优先级用1~10表示。数字越大优先级越高。在Thread的类中提供对线程设置优先级的方法,同时还定义了3个静态常量。如下:

setPriority(int priority):设置线程优先级MAX_PRIORITY:优先级最高级别,相当于10。MIN_PRIORITY:优先级最低级别,相当于1NORM_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()方法正常执行完,或者线程抛出未捕获的异常或错误,线程进入死亡状态。一旦进入死亡状态,线程将不再有运行的资格。

线程的生命周期图:

image


守护线程(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语句没有执行。