偏向锁:没有线程跟我争抢资源的,加个标记就可以了。
轻量锁:竞争时间比较小,比如使用CAS就可以解决的。
重量锁:竞争时间开销很大,需要利用操作系统的同步机制
乐观锁:对目标资源不加任何锁,失败了,可以接着尝试
悲观锁:当我要做操作时,我必须将资源锁起来,避免别人来干扰
可重入锁:我是当前资源锁的拥有者,我再次获取的时候,不需要释放再获取。例如ReentrantLock
不可重入锁:即使我是当前资源锁的拥有者,但是我再次获取这把锁,需要释放再获取
自旋锁:线程在当前资源获取锁失败后,并不直接阻塞或者等待,利用循环不断尝试获取锁
非自旋锁:和自旋锁相反的呗~拿不到锁直接放弃,进入等待或者阻塞状态
公平锁:先来先得的意思。拿不到锁的线程会进入等待状态,开始排队,一般等待时间最久的先获得锁。
非公平锁:插队了
不可中断锁:一旦开始进入竞争锁的状态,就不可以中途退出,不能说我不要锁了,不想等待了,比如synchronized
可中断锁:可以随时退出竞争锁的状态,例如Reentrant Lock
自适应的自旋锁:jvm已经都优化好了,不用自己实现。自旋锁一直重复获取锁,其实对cpu消耗还是很大的。所以后来优化成了自适应的自旋锁,可以根据获取的成功率失败率,当前锁拥有者的状态等等,来决定自旋的周期时间等等。
还有锁消除、粗化之类的一系列优化~
乐观锁的一种,它不会对目标资源加锁。它的过程看图吧
1.线程A获取到当前的内存值V为1,与自己的预期值1相等,将资源修改为2。
2.因为CAS对目标资源并不加任何锁,所以会发生A在修改前,B先完成了修 改,这样A获取到的当前内存值V就
为3,与自己的期望值A 1不相等,不修改。
使用场合
concurrentHashMap在没有产生hash碰撞时,直接插入数组,会使用CAS
原子类AtomicInteger
底层是通过monitor锁来实现的。synchronized在jvm中的锁优先级 偏向锁---->轻量锁—>重量锁
1.同步代码块的实现
代码如下:
public class Atest {
public void test(){
synchronized(this){
System.out.println("meng");
}
}
}
反编译结果
这里呢~我们可以看到一个monitorenter,两个monitorexit指令。
monitorenter指令获得锁,monitorexit释放锁。为啥俩monitorexit这个呢~因为jvm要保证线程正确结束时执行释放锁,遇到异常结束的话也要执行,所以jvm会在这两个位置分别插入一个monitorexit指令。
monitor锁存在于对象的对象头中,所以这也就是为啥说,一个Java对象可以作为锁。同时,每个对象维护着一个计数器,初始值0。monitorenter命令会使计数器+1。monitorexit释放锁,使计数器-1。
线程A来获取monitor锁,首先判断计数器是否为0,为0则获取锁,计数器+1。此时,线程B来获取锁发现计数器为1,则直接进入阻塞等待状态。
线程A在已拥有锁的情况下,再次申请获取锁,计数器会再次累加+1。只有执行monitorexit将计数器-1,直至为0时,其他阻塞等待的线程就可以来获取这把锁了。
2.同步方法
代码如下:
public class BTest {
public synchronized void test(){
System.out.println("mengmeng");
}
}
反汇编结果
同步方法与同步代码块不同,同步方法会增加一个ACC_SYNCHRONIZED 标识。jvm通过该标识来判断该方法是否为一个同步方法。然后来执行同步调用,获取monitor锁,执行方法,释放monitor锁。之后其余等待阻塞线程可再次获取锁。