Java并发编程基础知识学习笔记
问题的引入与解决步骤
线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见结果的问题。
并发编程为什么会出现线程安全问题,首先需要了解Java的内存模型。
JMM模型
Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存中的变量复制到自己的工作内存,然后对工作内存里的变量进行处理,处理完成后将变量更新到主内存。
Synchronized关键字
通常解决冲突最简单的方法就是,“我”使用的时候“别人”等着不要动。
线程执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出或抛出异常后或者在同步代码块内调用了wait系列方法时释放锁。
synchronized关键字实现原子性操作会引起线程上下文切换并带来线程调度开销,显得比较笨重。
Volatile关键字
volatile是Java虚拟机提供的轻量级的同步机制
1.保证可见性。
当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或其他地方,而是会把值刷新回主内存。其他线程读取该变量时,会从主内存重新获取最新值。
2.不保证原子性
非阻塞算法,读写变量值的时候没有加锁,不能保证操作的原子性。
3.禁止指令重排
保证指令执行顺序与实际代码中的编写顺序一致。
指令重排:Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。
CAS操作
CAS:Compare and Swap,这是JDK提供的非阻塞原子性操作,它通过硬件保证了比较——更新操作的原子性。
JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作。以getAndSetLong(Object o, long offset, long newValue)方法为例:
public final long getAndSetLong(Object o, long offset, long newValue) {
long v;
do {
//获取obj对象中偏移量为offset的变量对应的值
v = this.getLongVolatile(o, offset);
//比较offset与v的值是否相等
} while(!this.weakCompareAndSetLong(o, offset, v, newValue));
return v;
}
使用do-while循环是考虑到,多个线程同时调用的情况下CAS失败时进行重试。
ABA问题
ABA问题的产生是因为变量的状态值产生了环形转换。
比如当前存在两个线程:线程1和线程2 ; 共享变量value = 1。
Time1: 线程1获取value = 1
线程2获取value = 1
Time2: 线程1设置value = 2
线程2中value = 1
Time3: 线程1设置value = 1
线程2中value = 1
Time4: 线程1获取value = 1
线程2获取value = 1
虽然两个线程中的value值是相等的,但是线程2感受不到线程1的两次赋值操作。
解决方法:给每个变量的状态值都配备一个时间戳,从而避免ABA问题的产生。