1、Java提供的线程同步机制包括锁、volatile关键字、final关键字、static关键字以及一些相关的API
2、锁
- 锁有排他性,一次只能被一个线程持有
- 锁可以保证原子性、可见性、有序性
两个条件:
1、这些线程在访问同一组数据必须使用同一个锁
2、任意线程读写都需要持有相应的锁
- 可重入性ReentrantLock
/**
A调用B,B也用lock,但是A正持有lock,但是可重入性保证B申请lock成功
*/
void methodA() {
acquireLock(lock);
methodB();
releaseLock(lock);
}
void methodB() {
acquireLock(lock);
releaseLock(lock);
}
- 锁的粒度
1、定义:一个锁实例所保护的共享数据的数量大小
- 锁泄露
指一个线程获得锁之后一直无法释放锁而导致其他线程无法获得该锁
3、内部锁:synchronized
1、内部锁通过synchronized关键字实现
2、synchronized关键字修饰的代码块称为同步块
synchronized(锁句柄) {
//在此代码块访问共享数据
}
锁句柄变量通常使用private final
修饰
4、显式锁:Lock接口
1、排他锁,作用和内部锁相同
2、是java.util.concurrent.locks.Lock
接口的实例
3、公平锁保障锁调度的公平性往往是以增加上下文切换为代价的,因此显式锁默认非公平调度策略
4、读写锁:允许多个线程同时读取(只读)共享变量,但是一次只允许一个线程更新(读取后再更新)
满足以下两个条件选择读写锁:
1、只读操作比写(更新)操作要频繁得多
2、读线程持有锁的时间比较长
5、ReentrantReadWriteLock
可重入读写锁,支持锁的降级,即一个线程持有读写锁的写锁的情况下可以继续获得相应的读锁
5、线程同步机制的底层助手:内存屏障
1、Java虚拟机底层借助内存屏障Memory Barrier
实现两个动作:刷新处理器缓存和冲刷处理器缓存,保证可见性
6、锁与重排序
1、临界区外(临界区前、后)的操作可以被重排到临界区内
在编译(JIT动态编译)的时候,编译器可能将语句移到临界区内,然后在临界区开始前和结束后相应地插入获取屏障和释放屏障;而处理器不会再将这些被重排的语句重排到临界区外
7、轻量级同步:volatile
1、volatile的作用:保障可见性、有序性和保障long/double型变量读写操作的原子性
2、volatile仅保障对被修饰的变量的读写操作的原子性,如果要保障对volatile变量的赋值操作原子性,那这个赋值操作不能涉及任何共享变量(包括被赋值的volatile变量本身)的访问
3、如果修饰的是数组变量,volatile只能对数组引用本身的操作(读取数组引用和更新数组引用)起作用,无法对数组元素的操作(读取、更新数组元素)起作用
4、四个应用场景:
- 使用volatile变量作为状态标志
- 使用volatile保障可见性
- 使用volatile变量替代锁
- 实现简易版读写锁、
//典型例子:计数器
public class Counter {
private volatile long count;
public long value() {
return count;
}
public void increment() {
synchronized(this) {
count++;
}
}
}
8、CAS
1、Compare and Swap:一种处理器指令,能将read-modify-write和check-and-act之类操作转换为原子操作
2、CAS仅保障共享变量更新操作的原子性,不保障可见性
3、常见的原子变量类:AtomicInteger、AtomicLong、AtomicBoolean
9、对象发布
1、多个线程共享变量的途径
2、对象发布:使对象能够被其作用域之外的线程访问
3、发布的形式:
- 对象引用存储到public变量中
- 在非private方法中返回一个对象
- 创建内部类
- 通过方法调用将对象传递给外部方法
10、final关键字
1、保障被修饰的变量及其引用的对象在其被发布前是初始化完毕的
2、不能保障包含final字段的对象本身的可见性
3、不会导致上下文切换
11、static关键字
1、保障线程初次读取静态变量时可见性
2、保障静态变量被发布前是初始化完毕的