类型

Java基础回顾:各种锁_Java

 

 


1. 乐观锁 & 悲观锁


乐观锁与悲观锁是一种广义上的概念。



  • 悲观锁:对于同一个数据,悲观锁认为线程在使用数据时,总有其他线程来修改数据,因此在获取数据的时候回先加锁,确保数据不会被其他线程修改。java中Synchronized关键字和Lock接口实现类都是悲观锁。如下:
  • Java基础回顾:各种锁_公平锁_02



  • 乐观锁:乐观锁认为自己在使用数据时不会有其他线程来修改数据,所以不会添加锁。指示在更新数据的时候去判断数据是否有被其他线程更新。如果这个数据没被更新,则当前线程获取数据并更新,如果数据已经被其他线程修改,则根据不同的实现方式执行不同的操作(例如报错或自动重试)。乐观锁在Java中是通过无锁编程来实现,最常采用的CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
  • CAS:一种无锁算法,Compare And Swap(比较交换)。
  • 需要读写的内存位置V
  • 需要进行比较的预期值A
  • 需要写入的新值U


    CAS 存在问题:


      ABA问题(加版本号解决),循环时间长开销大,只能保证一个共享变量 的原子操作。


悲观锁是和写操作多的场景,先加锁可以保证数据正确性;


乐观锁适合读操作多的场景,不加锁的特性可以使读操作的性能大大提升。


 


2. 自旋锁 & 适应性自旋锁


自旋锁实现原理:CAS


例:AtomicInteger自增操作


 Java基础回顾:各种锁_乐观锁_03

 

 


适应性自旋锁:自旋的次数不再固定,由前一次在同一个锁上的自旋时间及锁的拥有者状态来决定。


 Java基础回顾:各种锁_重入锁_04

 

 


 


3. 无锁 & 偏向锁 & 轻量级锁 & 重量级锁


Java基础回顾:各种锁_公平锁_05

 

 


无锁:没有对资源进行锁定,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,实现:CAS


偏向锁:指一段同步代码一直被同一个线程访问,那么该线程会自动获取锁,降低获取锁的代价。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。


jdk6以后默认开启 JVM参数 -XX:-UseBiasedLocking=false


轻量级锁:当锁是偏向锁时,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的方式获取锁,不会阻塞,从而提高性能。CAS+自旋


重量级锁:若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。


 Java基础回顾:各种锁_数据_06

 

 


4. 公平锁 & 非公平锁


公平锁:多个线程按照申请锁的顺序来获取锁,线程在队列中排队,队列中第一个线程获取锁。线程不会饿死,但是效率低。


非公平锁:线程直接尝试去获取锁,获取不到才进入队列等待。效率高,但是线程有可能饿死。


例:ReentrantLock有一个内部类Sync,Sync继承AQS(AbstractQueuedSybchronizer),加锁和释放锁多事类Sync实现,有公平锁(FairSync extends Sync)和非公平锁(NonfairSync extends Sync),默认使用非公平锁。


Java基础回顾:各种锁_乐观锁_07

 

 


5. 可重入锁 & 非可重入锁


可重入锁:同一个线程可以重复获取锁,获取一次锁status 加1,释放一次锁status 减1,可重入锁的一个优点是可一定程度避免死锁。ReentrantLock和Synchronized都是可重入锁。


非可重入锁:NonReentrantLock


 


6. 独享锁 & 共享锁


独享锁:排它锁,该锁只能被一个线程持有。


共享锁:可以被多个线程持有。


 


一点浩然气,千里快哉风!