深入剖析Java中的锁【原理、锁优化、CAS、AQS】
1、悲观锁与乐观锁
广义概念,体现的是看待线程同步的不同角度。
悲观锁:悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改。
锁实现:关键字synchronized、接口Lock(concurrent包下)的实现类
使用场景:写操作较多,先加锁可以保证写操作时数据正确。
乐观锁:乐观锁认为使用数据时不会有别的线程修改数据,所以不添加锁;只是在更新数据时去判断之前有没有其它线程更新了这个数据。
锁实现:CAS算法,例如AtomicInteger类的原子自增是通过CAS自旋实现
使用场景:读操作较多,不加锁的特点使读操作性能大幅提升。
1.1 悲观锁执行过程
阻塞 -> 唤醒,涉及上下文的切换(cpu时间片的切换,也即由一个线程切换到另一个线程执行)
1.2 乐观锁执行过程
不会进行加锁操作,所有线程同步竞争cpu的使用权。只会在更新数据的那个时刻,进行判断。
1.3 CAS算法
1.3.1 CAS自旋:
比较与交换算法:执行while循环,不断更新。
可以设定次数,若超过此阈值,则自旋结束;没有超过,自旋继续,直到更新成功为止。
自旋:重新load主内存中的V值到当前线程栈当中。
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
1.3.2 基于CAS实现的原子类关键方法
1.3.3 CAS存在哪些问题
ABA问题:V的值实际上已经被线程3更改过了,但是线程2并不能感知到V的值的变化。
1.3.4 自旋锁
CPU运行原理:多线程任务都是使用cpu的一段时间片,每个时间片对应一个线程的处理任务,时间片的切换就是上下文的切换。
自旋:通过CAS算法,不断进行比较与更新,去读取主内存中最新的状态值;
线程的阻塞与唤醒,需要进行上下文的切换,影响性能,耗资源的操作。
自旋就是避免阻塞与唤醒。
通过自旋就可以并发修改更新成功,不断尝试自旋,没有必要进行阻塞。
自适应自旋:会根据上一次的自旋次数,上下浮动 调整下一次自旋的次数。
若达到自旋次数,还没有更新成功,则更新失败结束自旋,抛出异常挂起线程或返回。
1.4 synchronized分析
注意:synchronized 关键字反编译的字节码指令是 monitorenter和 monitorexit,会被jvm识别,是否需要加锁与解锁。
1.4.1 Monitor
每个同步对象都有一个自己的Monitor(监视器锁)
1.4.2 JVM内置锁的膨胀升级
2、上下文切换
进程与线程上下文切换(消耗资源)原理解析?
- cpu需要从用户态切换到内核态;同时上下文切换过程中,要保存上一个线程执行的中间变量指令指针写回到PCB进程控制块;再去切换到新的线程执行任务;当新线程时间片用完,又需要把上一个中断的线程的中间变量从内存条中读回,接着上次中断的状态执行。
上一篇: $_SERVER['REMOTE_HOST']有关问题!求大牛指导一二
下一篇: CAS开始看源码了