iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Java并发中ReentrantLock锁怎么用
  • 865
分享到

Java并发中ReentrantLock锁怎么用

2023-06-21 21:06:33 865人浏览 八月长安
摘要

这篇文章主要讲解了“java并发中ReentrantLock锁怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中ReentrantLock锁怎么用”吧!重入锁可以替代关键字

这篇文章主要讲解了“java并发中ReentrantLock怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中ReentrantLock锁怎么用”吧!

    重入锁可以替代关键字 synchronized 。

    jdk5.0 的早期版本中,重入锁的性能远远优于关键字 synchronized ,

    但从 JDK6.0 开始, JDK 在关键字 synchronized 上做了大量的优化,使得两者的性能差距并不大。

    重入锁使用 ReentrantLock 实现

    1、重入锁

    package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo implements Runnable {    public static ReentrantLock lock = new ReentrantLock();    public static int i = 0;    @Override    public void run() {        for (int j = 0; j < 10000000; j++) {            lock.lock();            lock.lock();            try {                i++;            } finally {                lock.unlock();                lock.unlock();            }        }    }    public static void main(String[] args) throws InterruptedException {        ReentrantLockDemo tl = new ReentrantLockDemo();        Thread t1 = new Thread(tl);        Thread t2 = new Thread(tl);        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println(i);    }}

    控制台打印

    20000000

    说明

    一个线程连续两次获得同一把锁是允许的。

    如果不允许这么操作,那么同一个线程在第 2 次获得锁时,将会和自己产生死锁。

    程序就会“卡死”在第 2 次申请锁的过程中。

    但需要注意的是,如果同一个线程多次获得锁,那么在释放锁的时候,也必须释放相同次数。

    如果释放锁的次数多了,那么会得到一个 java.lang.IllegalMonitorStateException 异常,反之,如果释放锁的次数少了,那么相当于线程还持有这个锁,因此,其他线程也无法进入临界区。

    2、中断响应

    对于关键字 synchronized 来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得这把锁继续执行,要么它就保持等待。

    而使用重入锁,则提供另外一种可能,那就是线程可以被中断。

    也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。

    有些时候,这么做是非常有必要的。

    比如,你和朋友约好一起去打球,如果你等了半个小时朋友还没有到,你突然接到一个电话,说由于突发情况,朋友不能如约前来了,那么你一定扫兴地打道回府了。

    中断正是提供了一套类似的机制。

    如果一个线程正在等待锁,那么它依然可以收到一个通知,被告知无须等待,可以停止工作了。

    这种情况对于处理死锁是有一定帮助的。

    下面的代码产生了一个死锁,但得益于锁中断,我们可以很轻易地解决这个死锁。

    package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class IntLock implements Runnable {    public static ReentrantLock lock1 = new ReentrantLock();    public static ReentrantLock lock2 = new ReentrantLock();    int lock;        public IntLock(int lock) {        this.lock = lock;    }    @Override    public void run() {        try {            if (lock == 1) {                lock1.lockInterruptibly();                try {                    Thread.sleep(500);                } catch (InterruptedException e) {                }                lock2.lockInterruptibly();            } else {                lock2.lockInterruptibly();                try {                    Thread.sleep(500);                } catch (InterruptedException e) {                }                lock1.lockInterruptibly();            }        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            if (lock1.isHeldByCurrentThread())                lock1.unlock();            if (lock2.isHeldByCurrentThread())                lock2.unlock();            System.out.println(Thread.currentThread().getId() + ":线程退出");        }    }    public static void main(String[] args) throws InterruptedException {        IntLock r1 = new IntLock(1);        IntLock r2 = new IntLock(2);        Thread t1 = new Thread(r1);        Thread t2 = new Thread(r2);        t1.start();        t2.start();        Thread.sleep(1000);        //中断其中一个线程        t2.interrupt();    }}

    控制台输出

    java.lang.InterruptedException

    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)

    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)

    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)

    at com.shockang.study.java.concurrent.lock.IntLock.run(IntLock.java:35)

    at java.lang.Thread.run(Thread.java:748)

    线程退出

    线程退出

    说明

    线程 t1 和 t2 启动后, t1 先占用 lock1 ,再占用 lock2。

    t2 先占用 lock2 ,再请求 lock1。

    因此,很容易形成 t1 和 t2 之间的相互等待。

    在这里,对锁的请求,统一使用 lockInterruptibly() 方法。

    这是一个可以对中断进行响应的锁申请动作,即在等待锁的过程中,可以响应中断。

    在代码第 56 行,主线程 main 处于休眠状态,此时,这两个线程处于死锁的状态。

    在代码第 58 行,由于 t2 线程被中断,故 t2 会放弃对 lock1 的申请,同时释放已获得的 lock2 。

    这个操作导致 t1 线程可以顺利得到 lock2 而继续执行下去。

    3、锁申请等待限时

    除了等待外部通知之外,要避免死锁还有另外一种方法,那就是限时等待。

    依然以约朋友打球为例,如果朋友退退不来,又无法联系到他,那么在等待 1 到 2 个小时后,我想大部分人都会扫兴离去。

    对线程来说也是这样。

    通常,我们无法判断为什么一个线程退迟拿不到锁。

    也许是因为死锁了,也许是因为产生了饥饿。

    如果给定一个等待时间,让线程自动放弃,那么对系统来说是有意义的。

    我们可以使用 tryLock() 方法进行一次限时的等待。

    tryLock(long, TimeUnit)

    下面这段代码展示了限时等待锁的使用。

    package com.shockang.study.java.concurrent.lock;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class TimeLock implements Runnable {    public static ReentrantLock lock = new ReentrantLock();    @Override    public void run() {        try {            if (lock.tryLock(5, TimeUnit.SECONDS)) {                Thread.sleep(6000);            } else {                System.out.println("get lock failed");            }        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public static void main(String[] args) {        TimeLock tl = new TimeLock();        Thread t1 = new Thread(tl);        Thread t2 = new Thread(tl);        t1.start();        t2.start();    }}

    控制台打印

    get lock failed
    Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    at com.shockang.study.java.concurrent.lock.TimeLock.run(TimeLock.java:20)
    at java.lang.Thread.run(Thread.java:748)

    说明

    在这里, tryLock() 方法接收两个参数,一个表示等待时长,另外一个表示计时单位。

    这里的单位设置为秒,时长为 5 ,表示线程在这个锁请求中最多等待 5 秒。

    如果超过 5 秒还没有得到锁,就会返回 false 。

    如果成功获得锁,则返回 true 。

    在本例中,由于占用锁的线程会持有锁长达 6 秒,故另一个线程无法在 5 秒的等待时间内获得锁,因此请求锁会失败。

    tryLock()

    ReentrantLock.tryLock() 方法也可以不带参数直接运行。

    在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请锁会成功,并立即返回 true 。

    如果锁被其他线程占用,则当前线程不会进行等待,而是立即返回 false 。

    这种模式不会引起线程等待,因此也不会产生死锁。

    package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class TryLock implements Runnable {    public static ReentrantLock lock1 = new ReentrantLock();    public static ReentrantLock lock2 = new ReentrantLock();    int lock;    public TryLock(int lock) {        this.lock = lock;    }    @Override    public void run() {        if (lock == 1) {            while (true) {                if (lock1.tryLock()) {                    try {                        try {                            Thread.sleep(500);                        } catch (InterruptedException e) {                        }                        if (lock2.tryLock()) {                            try {                                System.out.println(Thread.currentThread()                                        .getId() + ":My Job done");                                return;                            } finally {                                lock2.unlock();                            }                        }                    } finally {                        lock1.unlock();                    }                }            }        } else {            while (true) {                if (lock2.tryLock()) {                    try {                        try {                            Thread.sleep(500);                        } catch (InterruptedException e) {                        }                        if (lock1.tryLock()) {                            try {                                System.out.println(Thread.currentThread()                                        .getId() + ":My Job done");                                return;                            } finally {                                lock1.unlock();                            }                        }                    } finally {                        lock2.unlock();                    }                }            }        }    }    public static void main(String[] args) throws InterruptedException {        TryLock r1 = new TryLock(1);        TryLock r2 = new TryLock(2);        Thread t1 = new Thread(r1);        Thread t2 = new Thread(r2);        t1.start();        t2.start();    }}

    控制台输出

    My Job done
    12:My Job done

    说明

    上述代码采用了非常容易死锁的加锁顺序。

    也就是先让 t1 获得 lock1 ,再让 2 获得 lock2 ,接着做反向请求,让 t1 申请 lock2 , t2 申请 lock1 。

    在一般情况下,这会导致 t1 和 2 相互等待。

    待,从而引起死锁。

    但是使用 tryLock() 方法后,这种情况就大大改善了。

    由于线程不会傻傻地等待,而是不停地尝试,因此,只要执行足够长的时间,线程总是会得到所有需要的资源,从而正常执行(这里以线程同时获得 lock1 和 lock2 两把锁,作为其可以正常执行的条件)。

    在同时获得 lock1 和 lock2 后,线程就打印出标志着任务完成的信息“ My Job done”。

    4、公平锁

    在大多数情况下,锁的申请都是非公平的。

    也就是说,线程 1 首先请求了锁 A ,接着线程 2 也请求了锁 A 。

    那么当锁 A 可用时,是线程 1 可以获得锁还是线程 2 可以获得锁呢?

    这是不一定的,系统只是会从这个锁的等待队列中随机挑选一个。

    因此不能保证其公平性。

    这就好比买票不排队,大家都围在售票窗口前,售票员忙得焦头烂额,也顾不及谁先谁后,随便找个人出票就完事了。

    而公平的锁,则不是这样,它会按照时间的先后顺序,保证先到者先得,后到者后得。

    公平锁的一大特点是:它不会产生饥饿现象

    关于线程饥饿请参考我的博客——死锁、活锁和饥饿是什么意思?

    只要你排队,最终还是可以等到资源的。

    如果我们使用 synchronized 关键字进行锁控制,那么产生的锁就是非公平的。

    而重入锁允许我们对其公平性进行设置。

    它的构造函数如下:

    public ReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}

    当参数 fair 为 true 时,表示锁是公平的。

    公平锁看起来很优美,但是要实现公平锁必然要求系统维护一个有序队列,因此公平锁的实现成本比较高,性能却非常低下,因此,在默认情况下,锁是非公平的。

    如果没有特别的需求,则不需要使用公平锁。

    公平锁和非公平锁在线程调度表现上也是非常不一样的。

    下面的代码可以很好地突出公平锁的特点。

    package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class FairLock implements Runnable {    public static ReentrantLock fairLock = new ReentrantLock(true);    @Override    public void run() {        while (true) {            try {                fairLock.lock();                System.out.println(Thread.currentThread().getName() + " 获得锁");            } finally {                fairLock.unlock();            }        }    }    public static void main(String[] args) throws InterruptedException {        FairLock r1 = new FairLock();        Thread t1 = new Thread(r1, "Thread_t1");        Thread t2 = new Thread(r1, "Thread_t2");        t1.start();        t2.start();    }}

    控制台输出

    获得锁

    Thread_t2 获得锁

    Thread_t2 获得锁

    Thread_t2 获得锁

    Thread_t2 获得锁

    Thread_t1 获得锁

    Thread_t1 获得锁

    Thread_t2 获得锁

    Thread_t2 获得锁

    Thread_t2 获得锁

    Thread_t1 获得锁

    Thread_t1 获得锁

    # 省略

    说明

    由于代码会产生大量输出,这里只截取部分进行说明。

    在这个输出中,很明显可以看到,两个线程基本上是交替获得锁的,几乎不会发生同一个线程连续多次获得锁的可能,从而保证了公平性。

    如果设置了 false,则会根据系统的调度,一个线程会倾向于再次获取已经持有的锁,这种分配方式是高效的,但是无公平性可言。

    源码(JDK8)

    public class ReentrantLock implements Lock, java.io.Serializable

    感谢各位的阅读,以上就是“Java并发中ReentrantLock锁怎么用”的内容了,经过本文的学习后,相信大家对Java并发中ReentrantLock锁怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

    --结束END--

    本文标题: Java并发中ReentrantLock锁怎么用

    本文链接: https://www.lsjlt.com/news/300733.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • Java并发中ReentrantLock锁怎么用
      这篇文章主要讲解了“Java并发中ReentrantLock锁怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中ReentrantLock锁怎么用”吧!重入锁可以替代关键字 ...
      99+
      2023-06-21
    • 浅谈Java并发中ReentrantLock锁应该怎么用
      目录1、重入锁说明2、中断响应说明3、锁申请等待限时tryLock(long, TimeUnit)tryLock()4、公平锁说明源码(JDK8)重入锁可以替代关键字 synchro...
      99+
      2024-04-02
    • java ReentrantLock并发锁使用详解
      目录一、ReentrantLock是什么1-1、ReentrantLock和synchronized区别1-2、ReentrantLock的使用1-2-1、ReentrantLock...
      99+
      2022-11-13
      java ReentrantLock并发锁 java ReentrantLock
    • java高并发的ReentrantLock重入锁
      目录synchronized的局限性ReentrantLockReentrantLock基本使用ReentrantLock是可重入锁ReentrantLock实现公平锁Reentra...
      99+
      2024-04-02
    • java并发编程中ReentrantLock可重入读写锁
      目录一、ReentrantLock可重入锁二、ReentrantReadWriteLock读写锁三、读锁之间不互斥一、ReentrantLock可重入锁 可重入锁ReentrantL...
      99+
      2024-04-02
    • 怎么在JAVA中使用ReentrantLock实现并发
      这期内容当中小编将会给大家带来有关怎么在JAVA中使用ReentrantLock实现并发,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. 介绍结合上面的ReentrantLock类图,Reentrant...
      99+
      2023-06-15
    • Java多线程并发ReentrantLock怎么使用
      这篇文章主要介绍“Java多线程并发ReentrantLock怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java多线程并发ReentrantLock怎么使用”文章能帮助大家解决问题。背景...
      99+
      2023-07-02
    • Java 多线程并发ReentrantLock
      目录背景ReentrantLock可重入特性公平锁设置参数源码分析Lock 接口加锁操作内部类SynctryLockinitialTryLocklocklockInterruptib...
      99+
      2024-04-02
    • 怎么在Java中使用ReentrantLock实现并发编程
      这篇文章给大家介绍怎么在Java中使用ReentrantLock实现并发编程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、首先看图二、lock()跟踪源码这里对公平锁和非公平锁做了不同实现,由构造方法参数决定是否公...
      99+
      2023-06-15
    • 浅谈JAVA并发之ReentrantLock
      目录1. 介绍2. 源码剖析2.1 上锁(获取资源)2.2 释放资源2.3 公平锁与非公平锁的区别1. 介绍 结合上面的ReentrantLock类图,ReentrantLock实...
      99+
      2024-04-02
    • Java多线程并发之ReentrantLock
      目录ReentrantLock公平锁和非公平锁重入锁小结疑惑ReentrantLock 公平锁和非公平锁 这个类是接口 Lock的实现类,也是悲观锁的一种,但是它提供了 lock和 ...
      99+
      2023-05-18
      Java 多线程并发 Java ReentrantLock
    • Java AQS中ReentrantLock条件锁的使用
      目录一.什么是AQS1.定义2.特性3.属性4.资源共享方式5.两种队列6.队列节点状态7.实现方法二.等待队列1.同步等待队列2.条件等待队列三.condition接口四.Reen...
      99+
      2023-02-02
      Java ReentrantLock条件锁 Java ReentrantLock Java条件锁
    • Java并发编程之浅谈ReentrantLock
      目录一、首先看图二、lock()跟踪源码2.1 非公平锁实现2.1.1 tryAcquire(arg)2.1.2 acquireQueued(addWaiter(Node.EXCLU...
      99+
      2024-04-02
    • Java的并发锁怎么理解
      本篇内容主要讲解“Java的并发锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java的并发锁怎么理解”吧!   Java 中的并发锁大致分为隐式锁...
      99+
      2024-04-02
    • Java常用锁synchronized和ReentrantLock的区别
      目录区别1:用法不同synchronized 基础使用ReentrantLock 基础使用区别2:获取锁和释放锁方式不同区别3:锁类型不同区别4:响应中断不同区别5:底层实现不同小结...
      99+
      2024-04-02
    • Java并发编程之StampedLock锁怎么应用
      本篇内容介绍了“Java并发编程之StampedLock锁怎么应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!StampedLock:St...
      99+
      2023-06-30
    • 并发编程中如何使用Java中的锁?
      并发编程中如何使用Java中的锁? 在Java中,锁是一种用来控制多个线程访问共享资源的机制。锁可以保证在同一时刻只有一个线程可以访问共享资源,从而避免多个线程同时修改数据导致的数据不一致问题。Java中的锁可以分为两种类型:内置锁和显式锁...
      99+
      2023-08-28
      numy shell 并发
    • Java并发中死锁、活锁和饥饿是什么意思
      解答 死锁是指两个或者两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们将无法推进下去。 如果线程的智力不够, 且都秉承着“谦让”的原则,...
      99+
      2024-04-02
    • Java并发编程中死锁的实现
      这篇文章给大家介绍Java并发编程中死锁的实现,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、什么是死锁所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进二、死锁产生的...
      99+
      2023-06-15
    • java并发模型中的两种锁是什么
      这篇文章主要介绍java并发模型中的两种锁是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. 网页开发;5...
      99+
      2023-06-14
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作