第四章 Java 并发编程基础
4.1 线程
4.1.1 什么是线程
操作系统在运行一个程序时,会创建一个进程,如一个Java程序就对应一个 Java 进程。
一个进程可以创建多个线程,每个线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
处理器在这些线程间高速切换,让使用者感觉是在同时运行。
4.1.2 多线程
使用多线程的优势如下:
- 更好的利用多处理器核心
- 程序运行更快,以获得更快的响应时间
- 更好的编程模型
4.1.3 线程优先级
线程优先级决定了线程能够使用处理器的资源多少,默认优先级为5,优先级范围为 1~10.
针对频繁阻塞的(休眠或者 IO操作)线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。
4.1.4 线程的状态
Java 线程的六种生命周期:
状态 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还未调用 start() 方法 |
RUNNABLE | 运行状态,即操作系统中的就绪和运行两种状态 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,表示可以在指定时间自动返回的等待状态 |
TERMINATED | 终止状态,表示线程已执行完毕 |
4.1.5 Daemon 线程
当Java 虚拟机中不存在 Daemon 线程时,虚拟机将会退出。
通过 Thread.setDaemon(true) 将线程设置为 Daemon 线程。
4.2 启动、终止线程
通过调用线程的 start() 方法启动,随着 run() 方法的执行完毕,线程随之终止。
4.2.1 中断
中断表示一个运行中的线程是否被其他线程进行了中断操作。
中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。
4.2.2 安全地终止线程
通过标识位或者中断操作的方式,能够使线程在终止时有机会去清理资源,而不是武断地将线程停止。
通过标识位和中断来停止线程:
public class TestRunner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while(on && !Thread.currentThread().isInterrupted()){
i++;
}
}
public void cancel(){
on = false;
}
}
4.3 线程间通信
Java 支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝,所以程序在执行过程中,各线程看到的变量并不一定是最新的。
4.3.1 volatile 和 synchronized 关键字
volatile 关键字:用来修饰的变量,可以使访问该变量的线程均从共享内存中获取。且对共享变量的修改,必须同步刷新回共享内存中。但是过多地使用volatile 会降低程序执行的效率。
synchronized 关键字:用来修饰方法或者同步块,确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性和排他性。
4.3.2 等待/通知机制
等待/ 通知机制,是指一个线程A 调用了对象O 的wait() 方法而进入等待状态。线程B 调用了对象O 的notify()或notifyAll() 方法,线程A 收到通知后从对象O的wait() 方法返回,进而执行后续操作。
等待通知的经典范式:
等待方(消费方):
- 获取对象的锁
- 如果条件不满足,则调用对象的wait()方法,被通知后仍要检查条件
- 条件满足则执行对应的逻辑
synchronized(对象){
while(条件不满足){
对象.wait();
}
处理逻辑
}
通知方(生产者):
- 获得对象的锁
- 改变条件
- 通知所有等待在对象上的线程
synchronized(对象){
改变条件
对象.notifyAll();
}
4.3.3 Thread.join()
如果一个线程 A 执行了 thread.join() 语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join() 返回。
4.4 线程应用实例
4.4.1 等待超时模式
调用一个方法后,如果在等待时间内得到结果则立即返回,否则返回默认结果。
// 伪代码:
public synchronized Object get(long mills) throws InterruptedException {
long future = System.currentTimeMills() + mills;
long remaining = mills;
while((result == null) && remaining > 0){
wait(remaining);
remaining = future - System.currentTimeMills();
}
return result;
}