参考:
《Java并发编程的艺术》第四章
《Java多线程编程核心技术》
博客 https://www.jianshu.com/p/8a04b5ec786c Java多线程基础
博客 https://www.jianshu.com/p/12af2d966c13 Java并发编程1
一.线程简介
1.线程和进程
- 进程是系统进行资源分配和调度的一个独立单位,现代操作系统运行程序时会创建进程
- 线程也叫轻量级进程,是现代操作系统调度的最小单元,一个进程中可以有多个线程,每个线程都拥有各自的计数器、堆栈和局部变量等属性,并能够访问共享的内存变量
2.为什么使用多线程
- 更多的计算核心:充分利用多核处理器的硬件优势,将计算逻辑分配给多个处理器同时执行
- 更快的响应时间:在业务逻辑复杂的场景中,将数据一致性不强的操作派发给其他线程处理
- 更好的编程逻辑:Java提供了良好并且一致的多线程编程模型,方便开发者完成多线程开发
3.上下文切换
- CPU切换运行的线程时存储和恢复CPU的过程,使得线程能够从中断点恢复执行
- 线程上下文切换过程中会记录程序计数器、CPU寄存器状态等
- 多线程环境中上下文切换会带来一定的性能开销
4.线程优先级
- 现代操作系统采取分时的形式调度执行的线程
- 线程在获取操作系统分出的时间片后开始执行,时间片用完后停止执行,等待再次获得时间片
- 线程优先级决定线程获取CPU时间片的优先级
- 注意:Java线程优先级在某些操作系统中无效
5.线程的状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但还没有调用start() 方法 |
RUNNABLE | 运行状态,包括线程在操作系统中就绪和运行两种情况 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,进入等待状态的线程需要等待其他线程的特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,进入超时等待状态的线程可以在指定的时间自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
6. Daemon线程
- 支持型线程,用作程序中后台调度以及支持型工作
- JVM不存在非Daemon线程时,Daemon线程将自动结束,JVM退出
- 注意,在JVM退出时,Daemon线程中的finally块可能不会执行
二.线程启动和终止
1.构造线程
-
void init(ThreadGroup g, Runnable target, String name,long stackSize,AccessControlContext acc)
方法完成线程构造 - 新构造的线程对象由其parent线程进行空间分配,继承了parent线程的Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时获得唯一的线程ID
2.线程的实现
- 继承Thread类,重写
run()
方法,Thread类本身实现了Runnable接口 - 实现Runnable接口,重写
run()
方法 - 使用ExecutorService、Callable、Future实现有返回结果的多线程
3.线程中断
- 一个线程应该自行停止,而非由其他线程强制中断或停止
-
Thread.stop()
不保证资源的正确释放、Thread.suspend()
暂停时不释放锁容易导致死锁、Thread.resume()
等三个方法都已废弃 - 每个线程均有一个中断标志位,表示是否有其他线程对该线程进行了中断操作
- 当对一个线程调用
interrupt()
方法时
1)如果线程处于等待状态(如sleep、wait、join)时,线程将立即退出等待状态,并抛出一个interruptedException异常,仅此而已
2)如果线程处于正常活动状态,会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响 - 所以
interrupt()
并不能真正的中断线程,需要被调用的线程进行配合。如果一个线程有被中断的需求,可以这样做
1)在正常运行任务时,使用isInterrupted()
方法经常检查本线程的中断标志位,如果被设置了中断标志就自行停止
2)线程处于等待状态时,catch到InterruptedException异常后退出线程 -
Thread.interrupted()
方法将清除中断标志位,但并不代表线程又恢复,仅代表线程已响应该中断信号然后重置为可再次接收信号的状态 - 处于等待的线程在调用
interrupt()
方法后抛出InterruptedException前,JVM将先清除线程的中断标志位
Modifier and Type | Method | Description |
---|---|---|
void | interrupt() | Interrupts this thread |
static boolean | interrupted() | Tests whether the current thread has been interrupted |
boolean | isInterrupted() | Tests whether this thread has been interrupted |
三.线程间通信
1.volatile和synchronized
- Java支持多个线程同时访问共享对象,现代多核处理器为了加速程序运行,每个线程会拥有共享对象的一份拷贝,由此引出内存可见性问题——一个线程看到的变量并一定是最新的
-
volatile
:修饰的字段(成员变量),要求程序对该变量的访问必须从共享内存获取,修改必须同步刷新回共享内存,从而保证所有线程对变量访问的可见性 -
synchronized
:修饰方法或同步块,确保同一时刻,只有一个线程处于方法或同步块中,保证了线程对变量访问的可见性和排他性 - 对象、对象的监视器、同步队列和执行线程之间的关系
1)任意线程对由synchronized保护的object的访问,首先要获得object的监视器Monitor
2)如果Monitor获取失败,线程进入同步队列SynchronizedQueue,线程为BLOCKED状态
3)当其他获得锁访问object的线程释放锁,该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试获取object的Monitor
2.等待/通知机制
- 生产者消费者模式
1)线程A修改了一个对象的值,线程B在感知变化后进行相应的操作
2)整个过程开始于线程A(生产者),最终执行于线程B(消费者)
3)该模式隔离了“做什么”(What)和“怎么做”(How),功能层面实现了解耦 - 原始办法
消费者线程不断循环检查信号变量是否变化,伪代码如下,存在程序及时性和资源消耗量的两难
while ( value != desire ) {
Thread.sleep ( 1000 ) ;
}
doSometing();
-
等待/通知机制 notify/wait
1)线程A调用对象O的wait()
方法进入等待状态
2)线程B调用对象O的notify()
或notifyAll()
方法通知线程A
3)线程A收到通知后从对象O的wait()
方法返回,继续执行后续操作 -
等待/通知机制流程
Wait/Notify
1)使用wait()
、notify()
、notifyAll()
时需要先对调用对象加锁
2)调用wait()
后,线程状态由RUNNING变为WAITING,并将当前线程放置在等待队列
3)notify()
或notifyAll()
方法调用后,等待线程依旧不会从wait()
返回,需要调用notify()
或notifyAll()
的线程释放锁之后,等待线程才会从wait()
返回
4)notify()
/notifyAll()
将等待队列中的一个/全部等待线程从等待队列中移到同步队列,被移动的线程状态由WAITING变为BLOCKED
5)从wait()
方法返回(离开同步队列开始运行)的前提是获得了调用对象的锁
-
等待/通知的经典范式
等待方伪代码
1.获取对象的锁
2.如果条件不满足,则调用对象的wait()方法,被通知后仍要检查条件
3.条件满足则执行对应逻辑
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
通知方伪代码
1.获取对象的锁
2.改变条件
3.通知所有等待在对象上的线程
synchronized(对象) {
改变条件
对象.notifyAll();
}
3.管道输入/输出流
- 管道IO主要用于线程间数据传输,传输媒介为内存,与文件IO或网络IO不同
- 主要实现类
1)管道字节流:PipedOutputStream
、PipedInputStream
2)管道字符流:PipedReader
、PipedWriter
PipedReader in = new PipedReader();
PipedWriter out = new PipedWriter();
//必须连接输入流和输出流,否则抛出异常
out.connect(in);
4.Thread.join()
- 线程A使用thread.join()表示当前线程A等待thread线程终止后才从thread.join()返回
-
join()
方法的源代码逻辑结构与等待/通知经典范式一致,即加锁、循环和处理逻辑三步
5.线程变量ThreadLocal
- 以ThreadLocal对象为键、任意对象为值得存储结构,依附于线程
- 线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值
那年离别日,只道住桐庐。桐庐人不见,今得广州书
网友评论