java中的锁

这是从网上找的一张图,感觉脉络很清晰,就顺着这张图进行讲解java中锁的分类

 

Java中的锁有哪些种类?

  • 公平锁/和非公平锁
  • 乐观锁/悲观锁
  • 独占锁/共享锁
  • 自旋锁
  • 可重入锁
  • 互斥锁/读写锁
  • 偏向锁/轻量级锁/重量级锁
  • 分段锁
  • 分布式锁

以上有很多所得名词,这些状态不是全指锁的状态,有的是锁的特性,有的是所得设计

1)公平锁/非公平锁

公平锁:是指多个线程按照申请锁的顺序来获取锁

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程闲获得锁,有可能会造成优先级反转或者饥饿现象

对于Java ReentrantLock而言,通过构造函数指定锁是否是公平锁,默认是非公平锁.非公平锁的优点在于吞吐量比公平锁的大

public ReentrantLock() {
        sync = new NonfairSync();
    }

对于Synchronized而言,也是一种非公平锁,由于其并不像ReentrantLock 是通过AQS来实现的线程调度,所以没有办法使其变成公平锁

2)乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型锁,而是指看待并发同步的角度 

悲观锁认为对于同一数据的并发操作,一定会是修改的,哪怕没有修改,也会认为修改,因此对于同一个数据的并发操作,悲观锁采用的加锁的形式

乐观锁:则与之相反,认为对于同一个数据的并发操作,是不会发生修改的,在更新数据的时候,会采用不断更新,不断获取更新的数据

悲观锁:在java中的使用,就是各种锁

乐观锁:在java中,是无锁编程,长采用的CAS算法,典型的原子类,通过CAS自旋实现原子操作的更新

在mysql中乐观锁和悲观锁的具体实现,其中悲观锁,主要是表锁,行锁以及间隙锁  叶锁  读锁 ,因为这些所在触发的时候必然引起线程阻塞,所以叫悲观锁,另外乐观锁在mysql中本身并不存在的,但是mysql提供了mvcc的机制,支持乐观锁机制 

mvcc机制:多版本并发控制,保证数据操作在多线程过程中,保证事务隔离的机制,可以降低锁竞争的压力,保证高并发量,在这个过程中,每开启一个事务,会生成一个事务的版本号,被操作的数据会生成一个事务的版本号,被操作的数据会生成一条新的(临时)数据行,但是提交之前是对其他事务是不可见的,对于数据的更新成功,会将这个版本号更新到数据行中,事务提交成功,将新的版本号,更新到此数据行中,这样保证每个事物操作的数据,都是相互不影响的,不存在锁的问题

3)独享锁/共享锁

   独享锁是指该锁一次只能被一个线程所持有

   共享锁是指该锁可以被多个线程所持有

对于Java ReetrantLock而言,是独享锁,但是对于实现类是ReadWriteLock,其读锁是共享锁,其写是独享锁,独锁的共享锁可以保证并发读是非常高效的,读写,写读,写写的过程是互斥的,独享锁和共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享

  

4)自旋锁

在java中,自旋锁是指在尝试获取锁的线程不会立即阻塞,而是才用循环的方式去获取锁,这样的好处减少线程的上下文切换的消耗,缺点是循环会消耗CPU

可重入锁

又名递归锁,,是指在同一个线程在外层获取方法获取锁的时候,在进入内层方法会自动获取锁

对于Java  ReetrantLock 而言,从名字可以看出是一个可重入锁,对于synchjronized 而言,也是一个可重入锁,可重入锁的一个好处是可一定程度上避免死锁

synchronized void setA() throws Exception{
    Thread.sleep(1000);
    setB();
}

synchronized void setB() throws Exception{
    Thread.sleep(1000);
}

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

5)互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是 ReentrantLock
读写锁在Java中的具体实现就是 ReadWriteLock

6)偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态,并且是针对Synchronized  ,在java 5 通过引入锁升级的机制,来实现高效Synchronized,其中锁的状态是通过对象监视器在对象头中的字段表明的.

偏向锁是指一段同步代码一直被一个线程访问,哪么该线程会自动获取锁,降低获取锁的代价

轻量级锁是指当前锁是偏向锁的时候,被另一个线程访问,偏向锁就会升级为轻量级锁,其他线程通过自旋的形式获取锁,不会阻塞,提高性能

重量级锁是指在当前锁是轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能降低

7)分段锁

  分段锁,我们并不陌生,其实是一种锁的设计,对于ConCurrrentHashMap 而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作

  我们以ConcurrentHashMap 来说一下分段锁的含义和设计思想,ConcurrentHashMap中的分段锁被称为Segment,,他类似于HashMap(1.8以前)的结构,即内部拥有一个Entry的数组,而数组中的每一个元素就是一个链表;同时又继承ReentrantLock,当需要put元素的时候,并不是对整个hashmap进行加锁,而是通过hashMap来知道他要放入哪个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就可以实现真正的并行插入,但是在统计size的时候,可就是获取hashMap的全局信息,就需要获取所有分段锁下能统计.分段锁的设计目的是细化锁的粒度

8)分布式锁

提到分布式锁,不由想到CAP理论,重新回顾一下这个理论,任何一个分布式系统都无法同时满足一致性(Consistency)  可用性(Availability)  和分区容错性(Partition tolerance),最多只能满足两项,所以系统在设计之初就要对三者做出取舍,在互联网领域的绝大多数的场景中,都需要牺牲一致性来换取系统的高可用性,系统只需保证"最终一致性",只要这个最终时间用户可以接受即可

在很多场景中,我们为了保证数据的最终一致性,需要很多解决方案,比如部分布式事务,分布式锁.针对分布式锁的实现,目前比较常用的有一下几种解决方案

  • 基于数据库实现分布式锁
  • 基于缓存(Redis,memcached,tair)实现分布式锁
  • 基于Zookeeper实现分布式锁

 

posted on 2018-06-25 16:37  期待华丽转身  阅读(170)  评论(0编辑  收藏  举报