• Lock锁:ReentrantLock的可打断和可重入特性


    Lock 基于JDK层面实现的,Lock的实现主要有:

    ReentrantLock

    ReadLock

    WriteLock


     

    ReentrantLock

    JDK中独占锁(排他锁)的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

    ReentrantLock中的锁通过sync实现的,而sync继承了AbstractQueuedSynchronizer

     

    AbstractQueuedSynchronizer

    即(AQS)是JDK中实现并发编程的核心,平时我们工作中经常用到的ReentrantLock,CountDownLatch等都是基于它来实现的。底层用一个原子 int 值(status)来表示锁的状态,是其他同步器的基础
     

    ReentrantLock的应用

    特点:
    ⦁    可打断,可重入
    ⦁    可以设置超时时间
    ⦁    可以设置为公平锁(默认是非公平锁)
    ⦁    支持多个条件变量
    ⦁    支持读写锁

    1. 基本语法
    2. // 获取锁 reentrantLock.lock(); 
    3. try {
    4. // 处理执行区
    5. lock.lock();
    6. } finally {
    7. // 释放锁(避免try块中出现异常不能释放锁,而放于finally中)
    8. reentrantLock.unlock();
    9. }

     

    自旋锁

    轻量级锁在加锁过程中,用到了自旋锁。所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。
    注意:锁在原地循环等待的时候,是会消耗CPU资源的。所以自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么等待锁的线程会不断的循环反而会消耗CPU资源。在 JDK1.7 开始,引入了自适应自旋锁自适应意味着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。
      

    重入
    实现重进入功能

    重进入是指任意线程获取锁之后能够再次获取该锁而不会被锁阻塞

    锁的获取和释放过程如下:

        线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是则再次获取成功。每获取一次锁,这个status+1
        锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后(每释放一次锁,status-1),其他线程能够获取到该锁。锁的最终释放要求锁对获取进行计数(state)自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数器status等于0时表示锁已经成功释放了。
     

    可打断锁

    当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。

    在ReentrantLock的锁是可以中断的

    而ReentrantLock给我们提供了一个可以响应中断获取锁的方法lockInterruptibly()。该方法可以用来解决死锁问题。

     当线程被打断时当前线程将不再追求锁

    1. package com.lock;
    2. import org.apache.log4j.Logger;
    3. import java.util.concurrent.TimeUnit;
    4. import java.util.concurrent.locks.ReentrantLock;
    5. /**
    6. * 可打断
    7. */
    8. public class LockTest4 {
    9. static ReentrantLock lock = new ReentrantLock();
    10. static Logger log = Logger.getLogger(LockTest4.class);
    11. public static void main(String[] args) throws InterruptedException {
    12. //t2首先获取锁 然后阻塞5s
    13. new Thread("t2"){
    14. @Override
    15. public void run() {
    16. try {
    17. lock.lock();
    18. log.debug("t2获取了锁----");
    19. log.debug("睡眠5s 之后再执行");
    20. TimeUnit.SECONDS.sleep(5);
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. } finally {
    24. lock.unlock();
    25. log.debug("t2释放了锁----");
    26. }
    27. }
    28. }.start();
    29. TimeUnit.SECONDS.sleep(1); //主线程睡眠一秒
    30. //t1加锁失败因为被t2持有
    31. //t2首先获取锁 然后阻塞5s
    32. Thread t1 = new Thread(){
    33. @Override
    34. public void run() {
    35. try {
    36. lock.lockInterruptibly(); //可以被打断的锁
    37. log.debug("t1获取了可被打断的锁--执行代码");
    38. } catch (InterruptedException e) {
    39. e.printStackTrace();
    40. log.debug("t1被打断了没有获取锁");
    41. return;
    42. } finally {
    43. try {
    44. lock.unlock();
    45. log.debug("t1解锁");
    46. }catch (Exception e){
    47. e.printStackTrace();
    48. }
    49. }
    50. }
    51. };
    52. t1.start();
    53. //由于t1 可以被打断 故而1s之后打断t1 不在等待t2释放锁了
    54. try {
    55. log.debug("主线程在1s后 打断t1");
    56. TimeUnit.SECONDS.sleep(2);
    57. t1.interrupt();
    58. } catch (InterruptedException e) {
    59. e.printStackTrace();
    60. }
    61. }
    62. }

  • 相关阅读:
    TIA博途V17中ProDiag功能的使用方法示例(一)PLC数据类型的监控
    可编程 USB 转串口适配器开发板 S2S 功能介绍
    坚果云同步joplin笔记
    5个汇编考察问题
    DATA AI Summit 2022提及到的对 aggregate 的优化
    做完等级保护需要多长时间?
    【Hack The Box】Linux练习-- Popcorn
    Flowable 已经执行完毕的流程去哪找?
    docker部署博客项目
    阿里云99元ECS云服务器老用户也能买,续费同价!
  • 原文地址:https://blog.csdn.net/asmall_cat/article/details/125453738