锁从设计理念上可分为2类,分别为悲观锁(互斥锁)和乐观锁(非互斥锁)
悲观锁适用于写多读少的场景,乐观锁适用于读多写少的场景
java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
java中主要锁有2种实现方式,分别是jvm虚拟机实现的(Synchronized关键字)和JDK 代码实现的(Lock接口实现等java.util.concurrent.locks包下的锁)
Synchronized实现的锁是一种互斥锁(一次最多只能有一个线程持有的锁,当一个线程持有该锁的时候其它线程无法进入上锁的区域),它是一种悲观锁
synchronized最早只有重量级锁,在jdk1.6中对Synchronized进行了优化。
在编译器层面,使用了锁消除(对一些没有必要的、不会引起安全问题的同步代码取消同步)和锁粗化(对那些多次执行同步的代码且它们可以可并到一次同步的代码)
同时引进了适应性自旋锁,偏向锁,轻量级锁,这3种锁属于乐观锁。 jdk1.6中是默认开启偏向锁和轻量级锁的
Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。
锁可以升级但不能降级,这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。
偏向锁:(Mark Word 标志位为01时)线程首次进入偏向锁时,记录当前线程ID, 等到下一次执行该方法时,判断如果线程ID一致,就直接执行,啥也不做。只有第一次执行时需要CAS机制来设置。 如果再次执行该方法时,线程ID与原先保存的不一致,则撤销偏向后恢复到未锁定(标志位为‘01’)或轻量级锁定(标志位为‘00’)的状态。(偏向锁适用于那种,始终只有一个线程在执行一个方法的情况) 偏向锁的一个特性:持有锁的线程在执行完同步代码块时不会释放锁,当第二个线程执行到这个同步代码块时,会先去判断第一个线程是否存在,如果存在就将一个线程挂起,然后升级为轻量级锁,之后第一个线程继续执行,第二个线程自旋 如果不存在,则第二个锁持有该偏向锁,锁不升级 轻量级锁:(Mark Word 标志位为01时),虚拟机会用CAS操作去尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word, 如果更新成功,则状态位改为‘00’,进入到轻量级锁定状态, 如果更新失败,首先会检查对象的Mark Word是否指向当前线程的栈,如果是说明当前对象已经持有锁了,则直接进入同步块执行, 否则说明该锁被别的线程占用,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用适应性自旋锁来获取锁,自旋就是为了不让当前线程立刻阻塞,从而使CPU从用户态转为核心态。而采用循环等待一段时间去获取锁的过程。 轻量级锁解锁通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。替换成功,同步过程完成,替换失败说明其他线程尝试获取过该锁,在释放锁的同时要唤醒被挂起的线程
ps:锁小记
Synchronized只能是非公平锁,RetreenLock()和RetreenLock(false)是非公平锁,RetreenLock(true)是公平锁
JDK1.6以后优先考虑使用synchronized来进行同步
RetreenLock和Synchronized的区别
1、ReentrantLock 支持中断等待获取锁的线程,等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。
2、synchronized 必须在获取锁的代码块中释放锁,但是ReentrantLock 可以更加灵活的选择何时去释放锁。(不要忘记释放锁)
3、ReentrantLock 可实现公平锁和不公平锁
4、ReentrantLock 可以绑定多个条件,一个ReentrantLock对象可以同时绑定多个Condition对象(条件变量或条件队列),
而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地 添加一个锁
ReentrantReadWriteLock读写锁
读的时候加读锁,写的时候加写锁,读锁是共享锁,写锁是排他锁
在沒有任何读写锁时,才可以取得写入锁,读多写少的情况下,写入线程可能会遭遇饥饿的问题
jdk1.8推出了StampedLock,它是对ReentrantReadWriteLock的增强,优化了读锁、写锁的访问,同时使读写锁之间可以互相转换,更细粒度控制并发。
StampedLock是不可重入的,(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
StampedLock有三种锁的形式,分别是悲观写锁,悲观读锁,乐观读锁,前2种与ReentrantReadWriteLock中的写锁和读锁类似,增加了乐观读锁
ReentrantReadWriteLock只能从写锁降级为读锁,StampedLock读写锁可以互相转换
在ReentrantReadWriteLock中,当读锁被使用时,如果有线程尝试获取写锁,该写线程会阻塞。
但是,在Optimistic reading中,即使读线程获取到了读锁,写线程尝试获取写锁也不会阻塞,这相当于对读模式的优化,但是可能会导致数据不一致的问题。所以,当使用Optimistic reading获取到读锁时,必须对获取结果进行校验。
long stamp = lock.tryOptimisticRead(); // 非阻塞获取版本信息,乐观读锁 copyVaraibale2ThreadMemory(); // 拷贝变量到线程本地堆栈 if(!lock.validate(stamp)){ // 校验,是否有写锁,防止数据不一致 long stamp = lock.readLock(); // 获取悲观读锁 try { copyVaraibale2ThreadMemory(); // 拷贝变量到线程本地堆栈 } finally { lock.unlock(stamp); // 释放悲观读锁 } } useThreadMemoryVarables();
Atomic原子类都是使用CAS实现的
CopyOnWriteArrayList
读无影响,写的时候复制一个CopyOnWriteArrayList,然后进行修改,改完以后对象指针指向新复制的CopyOnWriteArrayList,读写分离的思想
来源:https://www.cnblogs.com/youmingDDD/p/10961643.html