本文部分内容节选自《Java并发编程的艺术》
🚀 基础(上) → 🚀 基础(中) → 🚀基础(下) → 🤩集合(上) → 🤩集合(下) → 🤗JVM专题1 → 🤗JVM专题2 → 🤗JVM专题3 → 🤗JVM专题4 →😋JUC专题1 → 😋JUC专题2
线程
现代操作系统调度的最小单元是 线程 , 也叫 轻量级进程 . 一个进程里可以创建多个线程, 线程拥有各自的计数器, 堆栈和局部变量, 并且能够访问共享的内存变量
线程优先级
现代操作系统使用时分的形式调度运行的线程, 操作系统会分出一个个时间片, 线程会分配到若干时间片, 当线程的时间片用完了就会发生线程调度, 并等待下次分配. 时间片多少决定了线程使用处理器资源的多少, 而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性
在 Java 线程中, 通过整型成员变量 priority
控制优先级, 优先级的范围是 1~10, 线程构建时可以通过 setPriority()
方法来修改优先级, 默认的优先级为 5. 优先级高的线程分配时间片的数量要多于优先级低的线程.
设置优先级时, 对于频繁阻塞(休眠或 I/O操作) 的线程需要设置较高优先级, 对于偏重计算的线程需要设置较低的优先级, 确保处理器不会被独占.
在不同JVM和不同操作系统上, 线程规划会存在差异, 有的操作系统会忽视对线程优先级的设定
线程状态
Java 线程在生命周期中可能处于如下的6种状态. 在给定的任意时刻, 线程只能处于其中一个状态
名称 | 说明 |
---|---|
NEW | 线程被创建, 但是没有调用 start() 方法 |
RUNNABLE | 允许状态, Java 线程将操作系统中就绪和运行两种状态笼统地称为 “运行中” |
BLOCKED | 阻塞状态, 表示线程被锁阻塞 |
WAITING | 等待状态, 表示线程进入等待状态, 进入该状态表示当前线程需要等待其他线程做出特定动作 |
TIME_WAITING | 超时等待, 该状态不同于WAITING, 它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态, 线程已经执行完毕 |
线程创建之后, 调用 start()
方法开始运行, 执行 wait()
方法, 线程进入等待状态, 进入等待状态的线程需要依靠其他线程的通知才能回到运行状态, 而超时等待状态相当于在等待状态的基础上增加了超时限制, 也就是超时时间到达时会自动恢复到运行状态. 当线程调用同步方法时, 在没有获取到锁的情况下, 线程会进入阻塞状态. 线程在执行 Runnable
的 run()
方法之后会进入到终止状态
Daemon线程
Daemon线程是一种支持型线程, 因为它主要被用作程序中后台调度和支持性工作. 这意味着, 当一个JVM中不存在非Daemon线程时, JVM会退出
可以通过 Thread
的 Thread.setDaemon(true)
将一个线程设置为 Daemon线程
必须在启动线程之前就设置好这个线程是否是Daemon线程, 不能在启动之后再设置, 否则会报错
启动和终止线程
构建线程
在运行线程之前要构造一个线程对象, 线程对象在构造时候需要提供线程所需要的属性, 如线程所属的线程组, 优先级, 是否为Daemon线程等信息
启动线程
线程对象在初始化完成之后, 调用 start()
方法就能启动线程.
中断
中断表示一个运行中的线程是否被其他线程进行了终端操作. 其他线程通过调用该线程的 interrupt()
方法对其进行中断操作
线程通过检查自身是否被中断来进行响应, 线程通过方法 inInterrupted()
来进行判断是否被中断, 也可以调用静态方法 Thread.interrupted()
对当前线程的中断标记位进行复位.
终止线程
中断操作是一种简便的线程间交互方式, 且这种交互方式最适合用来取消或停止任务. 除了中断以外, 还可以利用一个boolean变量来控制是否需要停止任务并终止该线程
线程间通信
volatile 和 synchronized
关键字 volatile 可以用来修饰字段, 告知程序任何对该变量的访问均需要从共享内存获取, 而对于它的改变必须刷新回到共享内存, 它能保证所有线程对变量访问的可见性
关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用, 它主要保证多个线程在同一时刻只有一个线程处于方法或同步代码块中, 保证线程对变量访问的可见性和排他性
等待/通知机制
一个线程修改了一个对象的值, 而另一个线程感知到了变化, 然后进行相应的操作, 整个过程开始于一个线程, 而最终执行又是另一个线程.
Java 通过内置的 等待/通知 机制解决了这个问题
等待/通知机制是所有Java对象都具备的, 因为这些方法被定义在 java.lang.Object
内部
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程, 使其从 wait() 方法返回, 返回的前提是该线程拿到了对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 调用该方法的线程进入 WAITING 状态, 只有等待另外线程的通知或者被中断才会返回, 需要注意, 调用 wait() 方法之后, 会释放对象的锁 |
wait(long) | 超市等待一段时间, 这里的参数单位是毫秒 |
wait(long, int) | 对于超时时间更细粒度的控制, 可以精确到纳秒 |
等待/通知机制, 是指一个线程 A 调用了对象 O 的 wait()
方法进入等待状态, 而另一个对象 B 调用了对象 O 的 notify()
或者 notifyAll()
方法, 线程 A 收到通知后从对象 O 的 wait()
方法返回, 进而执行后续操作. 上述两个线程通过对象 O 完成交互, 而对象上的 wait()
和 notify()/notifyAll()
就像是开关信号一样, 用来完成等待方和通知方之间的交互工作
使用 wait()
和 notify() / notifyAll()
需要注意的细节
- 使用
wait()
,notify()
和notifyAll()
时需要先对调用对象加锁 - 调用
wait()
方法后, 线程状态由RUNNING
变为WAITING
, 并将当前线程放置到对象的等待队列 notify()
或notifyAll()
方法调用之后, 等待线程仍然不会从wait()
返回, 需要调用notify()
或notifyAll()
的线程释放锁之后, 等待线程才有机会从wait()
返回notify()
方法将等待队列中的一个等待线程从等待队列中移到同步队列中, 而notifyAll()
方法则是将等待队列中所有的线程全部移到同步队列, 被移动的队列状态由WAITING
变为BLOCKED
- 从
wait()
方法返回的前提是获得了调用对象的锁
管道输入/输出流
管道输入/输出流主要用于线程之间的数据传输, 而传输的媒介是内存
管道输入/输出流有四种实现: PipedOutputStream, PipedInputStream, PipedReader, PipedWriter, 前两种面向字节, 后两者面向字符
Thread.join()
的使用
如果一个线程 A 执行了 thread.join()
语句, 其含义是: 当前线程 A 等待thread线程终止之后才从 thread.join()
返回. 线程 Thread 除了提供 join()
方法之外, 还提供了 join(long millis)
和 join(long millis, int nanos)
两个具备超时特性的方法. 这两个超时方法表示如果线程thread在给定的超时时间内没有终止, 那么将会从超时方法中返回
ThreadLocal 的使用
ThreadLocal, 即线程变量, 是一个以 ThreadLocal对象为键, 任意对象为值的存储结构, 这个结构被携带在线程上, 也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值
可以通过 set(T)
方法来设置一个值, 在当前线程下可以通过 get()
方法获取到原先设置的值