Java 线程基础


本文部分摘自《Java 并发编程的艺术》java


线程简介

1. 什么是线程?

现代操做系统在运行一个程序时,会为其建立一个进程,一个进程里能够建立多个线程。现代操做系统调度的最小单元是线程,也叫轻量级进程。这些线程都拥有各自的计数器、堆栈和局部变量等属性,而且能访问共享的内存变量。处理器在这些线程上高速切换,让使用者以为这些线程在同时执行编程

2. 为何使用多线程?

使用多线程的缘由主要有如下几点:安全

  • 更多的处理器核心多线程

    经过使用多线程技术,将计算逻辑分配到多个处理器核心上,能够显著减小程序的处理时间并发

  • 更快的响应时间ide

    有时咱们会编写一些较为复杂的代码(主要指业务逻辑),可使用多线程技术,将数据一致性不强的操做派发给其余线程处理(也可使用消息队列)。这样作的好处是响应用户请求的线程可以尽量快地处理完成,缩短了响应时间this

  • 更好的编程模型操作系统

    Java 已经为多线程编程提供了一套良好的编程模型,开发人员只需根据问题须要创建合适的模型便可线程


线程优先级

现代操做系统基本采用时分的形式调度运行的线程,操做系统会分出一个个时间片,线程分配到若干时间片,当线程的时间片用完了发生线程调度,并等待下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程须要多或少分配一些处理器资源的线程属性code

在 Java 线程中,经过一个整型成员变量 priority 来控制优先级,优先级的范围从 1 ~ 10,在线程构建时能够经过 setPriority(int) 方法来修改优先级,默认优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。不过,在不一样的 JVM 以及操做系统上,线程规划会存在差别,有些操做系统甚至会忽略线程优先级的设定

public class Priority {

    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws Exception {
        List<Job> jobs = new ArrayList<Job>();
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        for (Job job : jobs) {
            System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount);
        }
    }

    static class Job implements Runnable {
        private int priority;
        private long jobCount;

        public Job(int priority) {
            this.priority = priority;
        }

        @Override
        public void run() {
            while (notStart) {
                Thread.yield();
            }
            while (notEnd) {
                Thread.yield();
                jobCount++;
            }
        }
    }
}

运行该示例,在笔者机器上对应的输出以下

笔者使用的环境为:Win10 + JDK11,从输出能够看到线程优先级起做用了


线程的状态

Java 线程在运行的生命周期中可能处于下表所示的六种不一样的状态,在给定的一个时刻,线程只能处于其中的一个状态

状态名称 说明
NEW 初始状态,线程被构建,但还没调用 start() 方法
RUNNABLE 运行状态,Java 线程将操做系统中的就绪和运行两种状态笼统地称做“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程须要等待其余线程作出一些特定动做(通知或中断)
TIME_WAITING 超时等待状态,该状态不一样于 WAITING,它是能够在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

线程在自身的生命周期中,并非固定地处于某一状态,而是随着代码的执行在不一样的状态之间进行切换


Daemon 线程

Daemon 线程是一种支持型线程,主要被用做程序中后台调度以及支持性工做。这意味着,当一个 Java 虚拟机中不存在 Daemon 线程的时候,Java 虚拟机将退出。能够调用 Thread.setDaemon(true) 将线程设置为 Daemon 线程

使用 Daemon 线程须要注意两点:

  • Daemon 属性须要在启动线程以前设置,不能在启动线程以后设置
  • 在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行或关闭清理资源的逻辑。由于在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不必定会执行

启动和终止线程

1. 构造线程

在运行线程以前首先要构造一个线程对象,线程对象在构造的时候需提供线程需的属性,如线程所属的线程组、是不是 Daemon 线程等信息

2. 启动线程

线程对象在初始化完成以后,调用 start() 方法便可启动线程

3. 理解中断

中断能够理解为线程的一个标识位属性,标识一个运行中的线程是否被其余线程进行了中断操做。中断比如其余线程对该线程打了个招呼,其余线程能够经过调用该线程的 interrupt() 方法对其进行中断操做

线程经过检查自身是否被中断进行响应,线程经过 isInterrupted() 来进行判断是否被中断,也能够调用静态方法 Tread.interrupted() 对当前线程的中断标识位进行复位。若是线程已经处于终结状态,即时线程被中断过,在调用该对象的 isInterrupted() 时依旧会返回 false

许多声明抛出 InterruptedException 的方法在抛出异常以前,Java 虚拟机会先将该线程的中断标识位清除,而后抛出 InterruptedException,此时调用 isInterrupted() 方法将会返回 false

在下面的例子中,首先建立两个线程 SleepThread 和 BusyThread,前者不停地睡眠,后者一直运行,分别对两个线程分别进行中断操做,观察中断标识位

public class Interrupted {

    public static void main(String[] args) throws InterruptedException {
        // sleepThread 不停的尝试睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
        sleepThread.setDaemon(true);
        // busyThread 不停的运行
        Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
        busyThread.setDaemon(true);
        sleepThread.start();
        busyThread.start();
        // 休眠 5 秒,让 sleepThread 和 busyThread 充分运行
        TimeUnit.SECONDS.sleep(5);
        sleepThread.interrupt();
        busyThread.interrupt();
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        // 防止 sleepThread 和 busyThreaad 马上退出
        SleepUtils.second(2);
    }

    static class SleepRunner implements Runnable {

        @Override
        public void run() {
            while (true) {
                SleepUtils.second(10);
            }
        }
    }

    static class BusyRunner implements Runnable {

        @Override
        public void run() {
            while (true) {

            }
        }
    }
}

输出以下

从结果能够看出,抛出 InterruptedException 的线程 SleepThread,其中断标识位被清除了,而一直忙碌运行的线程 BusyThread 的中断标识位没有被清除

4. 安全地终止线程

前面提到的中断操做是一种简便的线程间交互方式,适合用来取消或中止任务。除了中断之外,还能够利用一个 boolean 变量来控制是否须要中止任务并终止线程

下面的示例中,建立了一个线程 CountThread,它不断地进行变量累加,而主线程尝试对其进行中断操做和中止操做

public class Shutdown {

    public static void main(String[] args) throws InterruptedException {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠一秒,main 线程对 CountThread 进行中断,使 CountThread 可以感知中断而结束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠一秒,main 线程对 Runner two 进行中断,使 CountThread 可以感知 on 为 false 而结束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }

    private static class Runner implements Runnable {

        private long i;
        private volatile boolean on = true;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        public void cancel() {
            on = false;
        }
    }
}

main 线程经过中断操做和 cancel() 方法都可使 CountThread 得以终止。这种经过标识位或者中断操做的方式可以使线程在终止时有机会去清理资源,而不是武断地将线程中止,更加安全和优雅

相关文章
相关标签/搜索