Java 并发笔记
JVM角度理解进程和线程的关系
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。程序计数器应该是私有的。
线程切换后能恢复到正确的执行位置。
为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的
线程安全和不安全
线程安全分析
成员变量和静态变量
成员变量和静态变量被共享了,且代码有读写操作,则代码是是临界区,则需要考虑线程安全问题。
局部变量
- 局部变量线程安全
- 局部变量的引用对象,如果逃离了方法的作用范围,则需要考虑线程安全。
局部变量暴露引用
eg. 子类重写父类方法,创建新线程导致线程变量被线程共享,且有读写操作。
- 父类方法用private或final关键词字防止方法被重写。体系了开闭原则。
线程的生命周期和状态
- NEW
- RUNNABLE(running和ready切换)
- BLOCKED:线程阻塞,需要等待锁释放
- WAITING:线程等待其他线程通知或中断
- TIME_WAITING
- TERMINATED
产生死锁的必要条件
- 互斥条件:该资源任意时刻只能被一个线程占用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件
- 循环等待
预防死锁?
- 一次性申请所有资源:破坏请求与保持
- 破坏不剥夺条件
- 破坏循环等待条件
避免死锁
volatile
happens-before 原则
- 程序顺序规则:一个线程内按照代码顺序,书写在前面的操作 happens-before 于书写在后面的操作
- 解锁规则:解锁happens-before于加锁
- volatile 变量规则:对 volatile 变量的写操作的结果对于发生于其后的任何操作都是可见的。
- 传递规则
- 线程启动规则:Thread对象的start()方法happens-before线程的每一个动作。
CAS: 输入2个值:旧值 和 新值,1、比较旧值有没有变化 -》 没变化则交换成新值。
乐观锁: 版本号,CAS
乐观锁存在的问题:
- ABA 解决:版本号或时间戳
- CAS经常自旋来重试,就是不成功就一直循环执行直到成功。
- 只能保证一个共享变量的原子操作 --> AtomicReference类保证引用对象间原子性。
构造方法不能使用 synchronized 关键字修饰。
线程池
如何创建线程池?
通过ThreadPoolExecutor构造函数来创建线程池。
通过 Executor 框架的工具类 Executors 来创建。
- FixedThreadPool : 创建固定线程数量的线程池。
- SingleThreadExcutor: 返回只有一个线程的线程池
- CachedThreadPool: 返回一个可根据实际情况调整线程数量的线程池。
- ScheduledThreadPool: 返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池。
线程池处理任务的流程
Semaphore 信号量
Semaphore(信号量)可以用来控制同时访问特定资源的线程数量。
// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();