快速入门Java多线程——基础篇

57 阅读7分钟

快速入门Java多线程将会分为三篇文章:基础篇、原理篇、JDK 工具篇

进程与线程基本概念

进程和线程的提出极大的提高了操作系统的性能。

进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。

进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。

本质区别是是否单独占有内存地址空间和其他系统资源。

上下文切换通常是计算密集型的,意味着此操作会消耗大量的 CPU 时间,故线程也不是越多越好。如何减少系统中上下文切换次数,是提升多线程性能的一个重点课题。

Java 多线程入门类和接口

Thread 与 Runnable

Runnable接口与Thread类
  • 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
  • Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
  • Runnable接口出现,降低了线程对象和线程任务的耦合性。
  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

Callable、Future 与 FutureTask

一般我们使用Runnable 但是Runnable没有返回值,所以引出Callable、Future 接口
  • Callable 一般配合线程池来使用,调用线程池的 submit 方法来让一个 Callable 接口执行。
  • Future和Callable可以用来给任务增加可取消性,所以可以使用Callable来代替Runnable。如果只是单纯增加可取消功能使用 Future 而不关心其结果,则可以声明Future<?>形式类型,并返回 null 作为任务结果。
  • Future 类的 cancel 方法是试图取消一个线程的执行,未必能取消成功。

Future接口有一个实现类叫FutureTaskFutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口

Future只是一个接口,而它里面的cancelgetisDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次

/**
  *
  * state可能的状态转变路径如下:
  * NEW -> COMPLETING -> NORMAL
  * NEW -> COMPLETING -> EXCEPTIONAL
  * NEW -> CANCELLED
  * NEW -> INTERRUPTING -> INTERRUPTED
  */
private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

线程组和线程优先级

ThreadGroup和Thread的关系就如同他们的字面意思一样简单粗暴,每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。

Java程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法决定的

一个线程默认是非守护线程,可以通过Thread类的setDaemon(boolean on)来设置。

总结来说,线程组是一个树状的结构,每个线程组下面可以有多个线程或者线程组。线程组可以起到统一控制线程的优先级和检查线程的权限的作用。

Java线程的状态及主要转化方法

操作系统线程主要有以下三个状态:

  • 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态。
  • 执行状态(running):线程正在使用CPU。
  • 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)。
// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

NEW

处于NEW状态的线程此时尚未启动。这里的尚未启动指的是还没调用Thread实例的start()方法。调用start不可以两次,threadStatus变量会被改变,不为 0 直接抛异常

RUNNABLE

表示当前线程正在运行中。处于RUNNABLE状态的线程在Java虚拟机中运行,也有可能在等待其他系统资源(比如I/O)。

Java线程的RUNNABLE状态其实是包括了传统操作系统线程的readyrunning两个状态的。

BLOCKED

阻塞状态。处于BLOCKED状态的线程正等待锁的释放以进入同步区。

WAITING

等待状态。处于等待状态的线程变成RUNNABLE状态需要其他线程唤醒(notify,notifyAll)。

调用如下3个方法会使线程进入等待状态

  • Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
  • Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法;
  • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。

TIME_WAITING

超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒。

调用如下方法会使线程进入超时等待状态:

  • Thread.sleep(long millis):使当前线程睡眠指定时间;
  • Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
  • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
  • LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
  • LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;

TERMINATED

终止状态。此时线程已执行完毕。

Java线程间的通信

在Java中,锁的概念都是基于对象的,所以我们又经常称它为对象锁。线程和锁的关系,我们可以用婚姻关系来理解。一个锁同一时间只能被一个线程持有。也就是说,一个锁如果和一个线程“结婚”(持有),那其他线程如果需要得到这个锁,就得等这个线程和这个锁“离婚”(释放)。
  • 直接用synchronized上锁,线程需要不断地去尝试获得锁,如果失败了,再继续尝试。这可能会耗费服务器资源。
  • 而等待/通知机制是另一种方式。Java多线程的等待/通知机制是基于Object类的wait()方法和notify(), notifyAll()方法来实现的。需要注意的是等待/通知机制使用的是使用同一个对象锁,如果你两个线程使用的是不同的对象锁,那它们之间是不能用等待/通知机制通信的。
  • 使用volatile信号量进行通信
  • 管道是基于“管道流”的通信方式。JDK提供了PipedWriterPipedReaderPipedOutputStreamPipedInputStream。其中,前面两个是基于字符的,后面两个是基于字节流的。
  • sleep方法是Thread类的一个静态方法。它的作用是让当前线程睡眠一段时间。sleep方法是不会释放当前的锁的,而wait方法会
    • wait可以指定时间,也可以不指定;而sleep必须指定时间。
    • wait释放cpu资源,同时释放锁;sleep释放cpu资源,但是不释放锁,所以易死锁
    • wait必须放在同步块或同步方法中,而sleep可以在任意位置
  • ThreadLocal是一个本地线程副本变量工具类。内部是一个弱引用的Map来维护。严格来说,ThreadLocal类并不属于多线程间的通信,而是让每个线程有自己”独立“的变量,线程之间互不影响。它为每个线程都创建一个副本,每个线程可以访问自己内部的副本变量。如果开发者希望将类的某个静态变量(user ID或者transaction ID)与线程状态关联,则可以考虑使用ThreadLocal。
  • InheritableThreadLocal类与ThreadLocal类稍有不同,Inheritable是继承的意思。它不仅仅是当前线程可以存取副本值,而且它的子线程也可以存取这个副本值。

参考

  1. redspider.gitbook.io/concurrent
  2. javaguide.cn/java/concur…