显式锁Lock和内置锁知识整理

(一)基础认识

(1)synchronized内置锁的不足之处

【1】synchronized内置锁无法中断一个正在等待获取锁的线程
【2】synchronized无法知道线程有没有成功获取到锁
【3】如果临界区是只读操作,其实可以多线程一起执行,但使用synchronized的话,同一时间只能有一个线程执行。
【4】synchronized不支持可轮询的、定时的以及中断的锁获取操作

(2)锁的几种分类

1. 可重入锁和非可重入锁

所谓重入锁,顾名思义。就是支持重新进入的锁,也就是说这个锁支持一个线程对资源重复加锁。
简单理解:线程A进去一个加锁的方法method1,此时方法内部有调用了加锁的方法method2,它可以重新进入这个锁,不会出现任何异常。

2. 公平锁与非公平锁

这里的“公平”,其实通俗意义来说就是“先来后到”,也就是FIFO。如果对一个锁来说,先对锁获取请求的线程一定会先被满足,后对锁获取请求的线程后被满足,那这个锁就是公平的。反之,那就是不公平的。

一般情况下,非公平锁能提升一定的效率。但是非公平锁可能会发生线程饥饿(有一些线程长时间得不到锁)的情况。所以要根据实际的需求来选择非公平锁和公平锁。

JDK提供的锁默认都是使用非公平锁,选择公平锁需要选择设置。

3. 读写锁和排它锁

synchronized用的锁和ReentrantLock,其实都是“排它锁”。也就是说,这些锁在同一时刻只允许一个线程进行访问。

而读写锁可以再同一时刻允许多个读线程访问。Java提供了ReentrantReadWriteLock类作为读写锁的默认实现,内部维护了两个锁:一个读锁,一个写锁。通过分离读锁和写锁,使得在“读多写少”的环境下,大大地提高了性能。

注意,即使用读写锁,在写线程访问时,所有的读线程和其它写线程均被阻塞。

(二)Lock

(1)ReentrantLock

ReentrantLock是一个非抽象类,它是Lock接口的JDK默认实现,实现了锁的基本功能。从名字上看,它是一个”可重入“锁,从源码上看,它内部有一个抽象类Sync,是继承了AQS,自己实现的一个同步器。同时,ReentrantLock内部有两个非抽象类NonfairSync和FairSync,它们都继承了Sync。从名字上看得出,分别是”非公平同步器“和”公平同步器“的意思。这意味着ReentrantLock可以支持”公平锁“和”非公平锁“。默认是非公平锁。

(2)轮询锁与定时锁

可定时的与轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。

(3)可中断的锁获取操作

lockInterruptibly方法能够在获得锁的同时保持对中断的响应,并且由于它包含在lock中,因此无需创建其他类型的不可中断阻塞机制。
当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就是说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

    Lock lock =new ReentrantLock();


    public void test1() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            //.....
        }
        finally {
            lock.unlock();
        }
    }

【注意】
1、由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

2、当一个线程获取了锁之后,是不会被interrupt()方法中断的。单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。

3、  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。这就是lock相对于synchronized可中断的锁获取操作的特性不同之处。

(4)非块结构的加锁

使用synchronized锁时,锁的获取和释放操作都是基于代码块的,释放锁的操作总是与获取锁的操作处于同一个代码块中,而不考虑控制权如何退出该代码块。自动的所释放操作简化了对程序的分析,避免了可能的编码错误,但有时候需要更灵活的加锁规则。
例如类似于ConcurrentHashMap锁分段技术,通过类似的原则来降低链表中锁的粒度,即为每一个链表节点使用一个独立的锁,是不同的线程能独立地对链表的不同部分进行操作。每次的加锁释放锁操作都是只针对于特定节点,这样的方式会在并发操作时效能更好。

(5)ReentrantReadWriteLock

这个类也是一个非抽象类,它是ReadWriteLock接口的JDK默认实现。它与ReentrantLock的功能类似,同样是可重入的,支持非公平锁和公平锁。不同的是,它还支持”读写锁“。

(6)Condition的使用以及和Object的监视器比较

每个对象都可以用继承自Object的wait/notify方法来实现等待/通知机制。而Condition接口也提供了类似Object监视器的方法,通过与Lock配合来实现等待/通知模式。

那为什么既然有Object的监视器方法了,还要用Condition呢?这里有一个二者简单的对比:

在这里插入图片描述
摘录:《深入浅出Java多线程》

Condition和Object的wait/notify基本相似。其中,Condition的await方法对应的是Object的wait方法,而Condition的signal/signalAll方法则对应Object的notify/notifyAll()。但Condition类似于Object的等待/通知机制的加强版。我们来看看主要的方法:

在这里插入图片描述

(7)synchronized和ReentrantLock的选择

1:功能层面上,ReentrantLock提供了内置锁synchronized更多的扩展,包括定时的加锁,可中断的锁等待,公平性,非块结构的加锁

2:Java5及以前的版本ReentrantLock的性能是远超synchronized,但是在Java6开始synchronized进行了优化,比如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步等等方式。Java6之后的版本ReentrantLock的性能略有胜出。

3:与显示锁相比,内置锁仍然具有很大的优势。内置锁开发人员更加熟悉且使用起来更加简单方便。不建议在系统中混合式的使用内置锁和显示锁。Lock锁的危险性比同步机制要高,如果忘记在finally块中调用unlock,那么虽然代码正常运行,但是如果发生问题将很难排查。

4:在Java6之后,仅当内置锁无法满足需求,或者相较于内置锁当前的业务场景更加适用于显示锁,否则更加建议使用内置锁来完成工作。

5:Java未来更可能提升synchronized而不是ReentrantLock的性能,因为synchronized是JVM的内置属性。

参考

《深入浅出Java多线程》
《Java并发编程实战》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZWZhangYu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值