Java并发编程实战——显示锁

显示锁

1.1 Lock与ReentrantLocksynchronized 的局限性 与 Lock 的优点

  • 内置锁的局限性:1、无法中断一个正在等待获取锁的线程; 2. 无法在请求获取一个锁时无限地等待下去?3. 内置锁无法响应中断
  • 内置锁的好处:与异常处理操作实现了很好的交互,当占有锁线程执行发生异常,此时JVM会让线程自动释放锁;而Lock的加锁和释放锁都需要手动执行,并且需要考虑抛出异常的情况,通常在finally代码块中释放锁。
  • ReentrantLock锁实现了Lock接口,更灵活,用途更广泛;

1.1.1 轮询锁与定时锁

  • 内置锁 开始请求锁后,这个操作将无法取消
  • 可定时的与可轮询的锁获取方式是由tryLock方法实现的,与无条件的锁获取方式相比,它具有更完善的错误回复机制
  • 避免死锁:轮询锁破坏请求保持条件:只要有一个资源得不到分配,也不给这个进程分配其他的资源;例如使用tryLock来获取两个锁,如果不能同时获得,那么就回退并重新尝试。
  • boolean tryLock():仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
  • boolean tryLock(long time, TimeUnit unit) :tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  • 在内置锁中,释放锁的操作总是与获取锁的操作处于同一个代码块

1.1.2 可中断的锁获取操作

  • 基于lockInterruptibly方法;

首先你得了解为什么Lock要提供tryLock和tryInterruptibly因为它要破坏死锁中的“不可抢占条件”。
synchronized没办法破坏“不可抢占条件”,synchronized申请资源的时候,如果申请不到,线程直接进入阻塞状态,无法释放已经占有的锁。
但我们希望的是:如果占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占条件被破坏掉了。所以,Lock提供了三种方法,均可以破坏不可抢占条件。这也是为什么有了synchronized还要搞一个Lock的原因之一。
lock():拿不到lock就不罢休,不然线程就一直block。
tryLock:立即返回,获得锁返回true, 没获得锁返回false;
tryLock(long time, TimeUint unit):拿不到lock,就等一段时间,超时返回false
tryInterruptibly:在锁上等待,直到获取锁,但是会响应中断,这个方法优先考虑响应中断,而不是响应锁的普通获取或重入获取。

  • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),对该线程调用interrupt()方法(中断-java中停止线程的机制)后,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
// ...
            @Override
            public void run(){
                try{
                    // 你想做的事
                }catch(InterruptedException e){
                    // 捕获异常后的代码处理
                }
                // 以及接下来的部分
            }
//

1.1.3 非块结构的加锁

  • 对链表的每个节点进行加锁,称为连锁式加锁(持有当前节点的锁,释放上一节点的锁,获取下一个节点的锁。使得不同的线程能独立地对链表的不同部分进行操作)

1.2 性能因素考虑

  • Java 5.0(JDK 1.5) ReetrantLock加入,比内置锁性能更好
  • Java 6.0 (JDK 1.8) 对内置锁做了算法改进,性能与显示锁差不多

1.3 公平性

  • ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平的锁(默认)或者一个公平的锁;

  • 内置锁是非公平锁;

  • 所谓公平锁就是,线程将按照它们发出的请求顺序来获取锁;而非公平锁,则允许“插队”;

  • 另外,还有放入队列的时机不一样;在公平的锁中,如果有另一个线程持有这个锁或者有其他线程在队列等待这个锁,那么新发出请求的线程将被放入队列中;而在非公平的锁中,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。

  • 在大多数情况下,非公平的锁性能会高于公平锁;原因是在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟,假设线程A持有一个锁,并且线程B在请求这个锁,由于锁被A占有,B被挂起,当线程A释放锁时,线程B将被唤醒。与此同时,有一个线程C也请求这个锁,那么可能会在被挂起线程B完全恢复之前获得、使用以及释放这个锁。这种情况是一种双赢的局面,B获得的锁时刻并没有延迟,线程C也更早地获得了锁,吞吐量得到了提升

  • 设计规则与思维模式:什么时候使用公平锁?

  • 当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,应该使用公平锁。因为这种情况下,线程C难以在短的时间内获得、使用以及释放这个锁,无法带来吞吐量的提升。

Java X=Java SE X=JDK1.X

1.4 在sychronized和ReentrantLock之间选择

  • 就性能而言,ReentrantLock 在java 5.0版本是远远胜出内置锁,而在java6.0版本只是略微胜出
  • 本书作者的观点是:
  • 仅当内置锁不能满足一些高级需求是则采用ReentrantLock,这些需求包括:可定时的、可轮询的、可中断的以及考虑公平性的锁获取操作,还有非块结构的加锁方式;否则,还是应该优先使用synchronized
    -因为,内置锁的优势在于:1. 被更多开发人员所熟悉,简洁紧凑,许多现有的程序中都已经使用了内置锁,如果将两种锁机制混用,容易发生错误并且可读性也不好;2. ReentrantLock的危险性比内置锁要高,如果忘记在finally块中调用unlock,那么相当于埋下了定时炸弹,很可能伤及其他代码;3. 未来更可能提升内置锁的性能,因为synchronized是JVM层面的内置属性,能够执行以一些优化; 4. 内置锁获取锁的操作能够与特定的栈帧关联起来,ReentrantLock 不可以。

1.5 读-写锁(ReadWriteLock接口 ReentrantReadWriteLock实现类)

  • 在哪种情况下可以使用读/写锁
    1、一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行;
    2、锁的持有时间较长(这种情况下多个读线程等待锁的时间就会越长,而读/写锁*支持多个读线程可以同时访问,省去了等待时间)
    3、在实际情况中,对于在多处理器系统上被频繁读取的数据结构,读-写锁能够提升性能,实现更高的并发性。而在其他情况下,读-写锁的性能比读-占锁略差,因为复杂性更高。
  • ReadWriteLock有多种实现方式(表现在读取锁与写入锁之间的交互方式):
    1、释放优先:当一个写入操作释放写入锁时,等待队列中同时存在读线程和写线程,那么优先选择读线程、写线程,还是最先发出请求的线程?
    2、读线程插队:如果锁是由读线程持有(注意没释放),但有写线程正在等待,那么新到达的读线程能否立即获得访问权?还是在写线程后面等待?如果允许读线程插队,那么将提高并发性,不过也可能造成写线程饥饿问题、
    3、重入性:读取锁和写入锁是否是可重入的;关于可重入锁的介绍:当前线程进入某个同步方法已进获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另一个同步方法也同样持有该锁。
    在这里插入图片描述
    4、降级:如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获得读取锁?由写入锁降级为读取锁,同时不允许其他写线程修改被保护的资源
    5、升级:读取锁能否优先于其他正在等待的读线程和写线程而升级成为一个写入锁? 大多数的读-写锁并不支持升级,因为容易造成死锁(如果两个读线程试图同时升级为写入锁,那么二者都不会释放读取锁(也就是没法结束读操作))
  • ReentrantReadWriteLock:在java5.0中,读取锁的行为类似于一个信号量而不是锁,只是维护读线程的数量。在java6.0中修改了此行为:记录的是哪些线程已经获得了读取锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值