线程简介


操作系统运行一个程序时会创建一个进程,而操作系统调度的最小单位是线程,一个进程中可以创建多个线程。例如一个视频播放器是一个进程,而其中音频和视频都可以分别看做一个线程。线程中都有各自的程序计数器、虚拟机栈和本地方法栈,并且能够访问操作共享内存变量。每个时刻单个处理器只运行一个线程,但由于处理器高速运行,宏观上感觉同时运行了多个程序。

Java程序从main方法开始执行,执行main方法的是一个main线程,一个Java程序的运行时main线程和其他线程同时运行。

为何使用多线程


现代处理器上核心数越来越多,如何更高效的利用多核处理器已是非常重要的课题。将计算逻辑分割为不同线程安排到不同核心上同时运算,可以很大程度提高运行效率。

有时候我们会编写一些非常复杂的业务逻辑,比如创建一笔订单,可能经过插入订单数据、生成订单快照、发送邮件等等。若用户从单击“提交”按钮开始,一直等很久才得到订单创建成功通知,很大程度上影响了用户的交互体验。但使用多线程,将一些数据一致性不是很强的操作分发给其他线程进行处理,能大大缩短响应时间。

线程优先级


现代操作系统都是采用分配时间片方式运行线程,当时间片用完就会发生线程调度,等待下次分配时间片。线程优先级决定了线程被分配时间片的多少。Java中,使用setPriority(int)来设置优先级,默认为5,范围是1~10。针对频繁阻塞线程设置较高优先级,而偏重计算即使用CPU较多的设置较低优先级,确保处理器不会被长久的独占。

程序的正确性千万不能依赖线程优先级来判断,就算设置了线程优先级,程序可能完全不会理会。

线程的状态


传统的进程一般有new、ready、running、waiting和terminated五种状态,具体的可参见相关操作系统书籍。在Java中,总共分为六种状态,详解见下表。

状态名称

说明

new

初始线程被构建,还未调用start()方法

runnable

运行,Java将就绪和运行笼统称为runnable

blocked

阻塞,阻塞于锁

waiting

等待,当前线程需要等待其他线程唤醒

timed_waiting

超时等待,超过一定时间可以自行苏醒

terminated

终止,线程执行完

下图详细展示了Java线程状态转化。

3e9e9f4ee5a5ba0e457c99f55c55e866.png阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时状态,但阻塞在java.util.concurrent包中Lock接口的线程却是等待状态,因为java.util.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中相关方法。 new关于start()这里思考两个问题:(1)能否反复调用start()方法?(2)若一个程序执行完毕,此时处于terminated状态,再次调用start()是否可行?下面先来看下start()源码。
public synchronized void start() {    if (threadStatus != 0)        throw new IllegalThreadStateException();    group.add(this);    boolean started = false;    try {        start0();        started = true;    } finally {        try {            if (!started) {                group.threadStartFailed(this);            }        } catch (Throwable ignore) {        }    }}
在start()方法内部有个threadStatus状态变量,若不为0,调用start()会抛出异常,这里我们可以知道,调用一次start()之后,threadStatus值会改变(即不为0),此时再调用start()会抛出IllegalThreadStateException异常。 runnable处于runnable状态线程可能在JVM中运行也可能在等待CPU分配资源,所以其包括了传统进程的ready和running两种状态。 blocked处于blocked状态的线程等待锁释放以进入同步区。 waiting处于等待状态的线程变成runnable状态需要其他线程唤醒。 timed_waiting线程等待一个具体的时间,时间到后会被自动唤醒。 terminated终止状态,此时线程已执行完毕。 Daemon守护线程
Daemon线程主要用作程序中后台调度,当一个Java虚拟机中不存在非Daemon线程时,Java虚拟机会退出。可以通过调用Thread.setDaemon(true)来将线程设置为Daemon线程。在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,如下代码示例。
public class Daemon {    static class DaemonRunner implements Runnable {        public static void main(String[] args) {            Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");            thread.setDaemon(true);            thread.start();        }        @Override        public void run() {            try {                Thread.sleep(10000);            } catch (InterruptedException e) {                e.printStackTrace();            } finally {                System.out.println("DaemonThread finally run.");            }        }    }}
命令行没有任何输出。main线程启动了线程DaemonRunner之后随着main线程执行完毕而终止,而此时JVM中没有非Daemon线程,虚拟机退出。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。 构造线程
运行线程前需要构建一个线程对象,需要提供线程属性,如线程所属线程组、线程优先级、是否是Daemon线程。如下是Thread类中初始化线程的init()方法。
private void init(ThreadGroup g, Runnable target, String name,                      long stackSize, AccessControlContext acc,                      boolean inheritThreadLocals) {    if (name == null) {        throw new NullPointerException("name cannot be null");    }            // 当前线程就是该线程的父线程    Thread parent = currentThread();        this.group = g;    this.daemon = parent.isDaemon();    this.priority = parent.getPriority();    this.target = target;    setPriority(priority);    if (inheritThreadLocals && parent.inheritableThreadLocals != null)        this.inheritableThreadLocals =                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);    this.stackSize = stackSize;    tid = nextThreadID();}
中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作,通过调用interrupt()进行中断操作。线程通过方法isInterrupted()方法判断是否被中断,可以调用Thread.interrupted()对当前线程的中断标识位进行复位。若该线程已是终结状态,即使该线程被中断过,此时isInterrupted也是false。 安全终止线程
中断操作是最适合用来取消或停止任务,除了中断外,还可以利用boolean变量来控制是否需要停止任务并终止该线程。示例代码如下。
public class ShutDown {    public static void main(String[] args) throws InterruptedException {        Runner one = new Runner();        Thread countThread = new Thread(one, "CountThread");        countThread.start();        Thread.sleep(1000);        countThread.interrupt();        Runner two = new Runner();        countThread = new Thread(two, "CountThread");        countThread.start();        Thread.sleep(1000);        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;        }    }}//Count i = 435742146//Count i = 388964025
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐