0.背景
1.为什么会有并发编程的概念呢?
因为硬件的发展,多核CPU的出现,为了更充分地利用多核CPU,可以让任务在多个核上并行地计算,以提升程序的计算效率。
2.应用场景:
如GUI程序,提升响应用户的速度;
如服务端 同时响应多个客户端请求,提升吞吐量和响应。
异步事件的简化处理
3.引入并发编程导致了哪些问题?
(1)线程安全问题
由于多个线程并发地访问线程共享的状态,如业务数据,数据缓存,数据库连接池等,可能会发生丢失更新的情况。
什么样的类是线程安全的呢?
(2)活跃性问题
当线程之间发生死锁,可能导致线程都阻塞,任务无法继续下去
(3)性能问题
线程之间的互相阻塞等待将会丢失部分并行性,内核线程的上下文切换、内存同步都有开销,导致性能的下降。
4.如何分辨一个对象是否线程安全?
- 无状态的对象一定是线程安全的。
5.怎样修复线程安全问题呢?
- 线程之间不共享该状态变量 ThreadLocal
- 将状态变量修改为不可变变量 final 如String
- 访问时 同步 synchronized
1.分工
使用多个线程并行处理子任务,使得大任务能够高效完成。
1.1 实现方法
1.1.1 创建线程
有3中方式创建线程,实现Runnable接口、实现Callable接口和继承Thread类,通常使用实现Runnable接口的方式
@Test
public void testStart() {
Thread B = new Thread(new Runnable() {
public void run() {
......
}
});
// 主线程启动子线程
B.start();
}
start()方法启动线程,线程状态流转如下图所示
1.1.2 线程池
管理工作线程的池子,具体请见
ThreadPoolExecutor
构造参数
参数名称 | 含义作用 |
corePoolSize | 基本大小-初始 |
maximumPoolSiz | 最大线程数 在workQueue满时创建新线程 |
keepAliveTime | 最大空闲存活时间 空闲超过这个时间,销毁线程 小与基本大小 |
unit | 时间单位 |
workQueue | 工作队列,存放任务 |
2.同步互斥
从上述引发的问题总结而言有以下3个需求需要解决:
- 有序性 CPU会进行编译优化指令,在不可预见的线程调度下,可能发生跟预期结果不一致
- 可见性 各个线程会将共享变量加载进自己的工作内存,可能不能实时看见别的线程修改了共享变量
- 原子性 有的几步操作不能被中断 ,但是中途线程切换了导致状态有问题
多个工作线程协同完成一个大任务,就像我们跟同事协同开发同一个分支,为了避免重复性劳动或者代码覆盖、或者阻塞等待的问题,我们需要及时地同步状态和进度,工作线程也需要。
2.1 同步互斥
其实就是将某部分代码从并发执行 改为 串行执行,从而避免并发执行可能引起的竞态问题。
2.1.1 synchronized
最简单的方式就是加上synchronized关键字,这是Java内置互斥重入锁。
public class SynchronizeExample {
private int x;
//同步块
public void synBlock(){
synchronized (this) { // 此处自动加锁
// x 是共享变量, 初始值 =10
if (this.x < 12) {
this.x = 12;
}
} // 此处自动解锁
// 规则4 一个锁的解锁 Happens-Before 于后续对这个锁的加锁
}
}
为什么也被称为对象锁(普通对象或者Class对象)呢?因为是CAS操作对象头实现互斥的,每个对象都有对象头和实例数据,对象头包含Mark Word和元数据指针,Mark Word中标记了该对象的锁状态,当对象处于偏向锁状态时保存了持有该对象的线程id,除了该线程可以重入该对象的所有同步块,其余对象都需要等待。synchronized实现https://www.jianshu.com/p/e2054351bd95
锁升级过程
2.1.2 ReentrantLock
除了synchronized之外,还可以用java.util.concurrent包里的ReentrantLock来同步,都是可以可重入互斥锁,是通过AQS实现的API层面的,比synchronized多了一些高级功能,如等待锁时是可以被中断的、可以传递参数实现公平锁、可以绑定多个条件Condition。
Lock lock = new ReentrantLock();
lock.lock();
try {
//.....
} finally {
lock.unlock();
}
3.协作
3.1 线程通信协作方式
在一些线程并发执行时,需要依赖别的线程操作,就像我们工作过程中要做工作B得等同事先把A做完,我们要一直阻塞等到同事告诉我们A已经完成,我们才被唤醒开始做B。此时两个线程需要通信,由于线程之间是共享主存的,而对象就存储在主存中,可以借助对象的共享变量传递信息给另一个线程。
Object的 wait¬ify 机制
Condition的await&signal 机制