并发编程-JUC-锁分类基础知识

锁分类

按上锁方式划分

隐式锁:synchronized
synchronized为Java的关键字,是Java提供的同步机制,当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只能有一个线程执行该代码。当使用synchronized修饰代码时,并不需要显式的执行加锁和解锁过程,所以它也被称之为隐式锁。
显式锁:JUC包中提供的锁
JUC中提供的锁都提供了常用的锁操作,加锁和解锁的方法都是显式的,我们称他们为显式锁。

按特性划分
  • 乐观锁/悲观锁:按照线程在使用共享资源时,要不要锁住同步资源,划分为:乐观锁和悲观锁。
    • 悲观锁:比较悲观,总是假设最坏的情况,对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
      • 实现:JUC的锁、Synchronized
    • 乐观锁:比较乐观,总是假设最好的情况,对于同一个数据的并发操作,乐观锁认为自己在使用数据时不会有别的线程修改数据,所以在获取数据的时候不会添加锁。只有在更新数据的时候才会去判断有没有别的线程更新了这个数据,如果这个数据没有被更新,当前线程将自己修改的数据成功写入;如果数据已经被其他线程更新,则会根据不同的情况执行不同的操作(例如:报错或自动重试)
      • 实现:CAS算法,关系型数据库的版本号机制
  • 可重入锁/不可重入锁:按照同一个线程是否可以重复获取同一把锁,划分为:可重入锁和不可重入锁。
    • 重入锁:一个线程可以重复获取同一把锁,不会因为之前已经获取了该锁未释放而被阻塞。在获得一个锁之后未释放锁之前,再次获得同一把锁时,只会增加获得锁的次数,当释放锁时,会同时减少锁定次数。可重入锁的一个优点是可一定程度避免死锁。
      • 实现:ReentrantLock、synchronized
    • 非重入锁:不可重入锁,与可重入锁相反,同一线程获得锁之后不可再次获取,重复获取会发生死锁。
  • 公平锁/非公平锁:按照多个线程竞争同一锁时需不需要排队,能不能插队,划分为公平锁和非公平锁。
    • 公平锁:多个线程按照申请锁的顺序来获得锁
      • 实现:new ReentrantLock(true)
    • 非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,允许“插队”,有可能后申请的线程比先申请的线程优先获取锁
      • 实现:new ReentrantLock(false),synchronized
  • 独享锁/共享锁:按照多个线程能不能同时共享同一个锁,锁被划分为独享锁和排他锁。
    • 独享锁(写锁):独享锁也叫排他锁,是指同一个锁同时只能被一个线程所持有。如果线程A对获得了锁S后,则其他线程只能阻塞等待线程A释放锁S后,才能获得锁S。
      • 实现:synchronized,ReentrantLock共享锁(读锁):同一个锁可被多个线程同时持有。如果线程A对获得了共享锁S后,则其他线程无需等待可以获得
    • 共享锁S。
      • 实现:ReentrantReadWriteLock的读锁。
    • 在ReentrantReadWriteLock维护了一对关联锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁”。ReadLock(读锁)用于读操作的,WriteLock(写锁)用于写操作,读锁是共享锁,写锁是独享锁,读锁可保证在读多写少的场景中,提高并发读的性能,增加程序的吞吐量。
其它分类
  • 自旋锁:获取锁失败时,线程不会阻塞而是循环尝试获得锁,直至获得锁成功。
    • 实现:CAS,举例:AtomicInteger#getAndIncrement()
  • 分段锁:在并发程序中,使用独占锁时保护共享资源的时候,基本上是采用串行方式,每次只能有一个线程能访问它。串行操作是会降低可伸缩性,在某些情况下我们可以将锁按照某种机制分解为一组独立对象上的锁,这成为分段锁。
    • 说的简单一点:容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
    • 实现:ConcurrentHashMap
  • 无锁/偏向锁/轻量级锁/重量级锁:
    • 这四个锁是synchronized独有的四种状态,级别从低到高依次是:无锁、- - 偏向锁、轻量级锁和重量级锁。它们是JVM为了提高synchronized锁的获取与释放效率而做的优化。四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

synchronized和JUC的锁对比

Java已经提供了synchronized,为什么还要使用JUC的锁呢?重复造轮子?
synchronized同步锁提供了一种排他式的同步机制,当多个线程竞争锁资源时,同时只能有一个线程持有锁,当一个线程获取了锁,其他线程就会被阻塞只有等到占有锁的线程释放锁后,才能重新进行锁竞争。
使用synchronized同步锁,线程会三种情况下释放锁:

  1. 线程执行完了同步代码块/方法,释放锁;
  2. 线程执行时发生异常,此时JVM会让线程自动释放锁;
  3. 在同步代码块/方法中,锁对象执行了wait方法,线程释放锁。
    从以上synchronized的特点,我们可以总结出两个不足之处:
    第一:synchronized同步锁的线程阻塞,存在有两个致命的缺陷:无法控制阻塞时长;阻塞不可中断。
  • 使用synchronized同步锁,假如占有锁的线程被长时间阻塞(IO阻塞,sleep方法,join方法等),由于线程在阻塞时没有释放锁,如果其他线程尝试获取锁,就会被阻塞只能一直等待下去,甚至会发生死锁,这样就会造成大量线程的堆积,严重的影响服务器的性能。
  • JUC的锁可以解决这两个缺陷:
    • tryLock(long time, TimeUnit unit)
    • lockInterruptibly()
      第二:读多写少的场景中,当多个读线程同时操作共享资源时,读操作和读操作不会对共享资源进行修改,所以读线程和读线程是不需要同步的。如果这时采用synchronized关键字,就会导致一个问题,当多个线程都只是进行读操作时,所有线程都只能同步进行,只能有一个读线程可以进行读操作,其他读线程只能等待锁的释放而无法进行读操作。
  • 在上述场景中,我们需要实现一种机制,当多个线程都都只是进行读操作时,使得线程可以同时进行读操作(共享锁)。synchronized同步锁,不支持这种操作。
  • JUC的ReentrantReadWriteLock锁可以解决以上问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值