返回顶部
首页 > 资讯 > 后端开发 > Python >浅谈Java并发中ReentrantLock锁应该怎么用
  • 493
分享到

浅谈Java并发中ReentrantLock锁应该怎么用

2024-04-02 19:04:59 493人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录1、重入锁说明2、中断响应说明3、锁申请等待限时tryLock(long, TimeUnit)tryLock()4、公平锁说明源码(jdk8)重入锁可以替代关键字 synchro

重入锁可以替代关键字 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)

11:线程退出

12:线程退出

说明

线程 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();
    }
}

控制台输出

11: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锁应该怎么用的文章就介绍到这了,更多相关ReentrantLock锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 浅谈Java并发中ReentrantLock锁应该怎么用

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

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

猜你喜欢
  • 浅谈Java并发中ReentrantLock锁应该怎么用
    目录1、重入锁说明2、中断响应说明3、锁申请等待限时tryLock(long, TimeUnit)tryLock()4、公平锁说明源码(JDK8)重入锁可以替代关键字 synchro...
    99+
    2024-04-02
  • 浅谈JAVA并发之ReentrantLock
    目录1. 介绍2. 源码剖析2.1 上锁(获取资源)2.2 释放资源2.3 公平锁与非公平锁的区别1. 介绍 结合上面的ReentrantLock类图,ReentrantLock实...
    99+
    2024-04-02
  • Java并发编程之浅谈ReentrantLock
    目录一、首先看图二、lock()跟踪源码2.1 非公平锁实现2.1.1 tryAcquire(arg)2.1.2 acquireQueued(addWaiter(Node.EXCLU...
    99+
    2024-04-02
  • Java并发中ReentrantLock锁怎么用
    这篇文章主要讲解了“Java并发中ReentrantLock锁怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中ReentrantLock锁怎么用”吧!重入锁可以替代关键字 ...
    99+
    2023-06-21
  • java ReentrantLock并发锁使用详解
    目录一、ReentrantLock是什么1-1、ReentrantLock和synchronized区别1-2、ReentrantLock的使用1-2-1、ReentrantLock...
    99+
    2022-11-13
    java ReentrantLock并发锁 java ReentrantLock
  • 怎么在JAVA中使用ReentrantLock实现并发
    这期内容当中小编将会给大家带来有关怎么在JAVA中使用ReentrantLock实现并发,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. 介绍结合上面的ReentrantLock类图,Reentrant...
    99+
    2023-06-15
  • java并发编程中ReentrantLock可重入读写锁
    目录一、ReentrantLock可重入锁二、ReentrantReadWriteLock读写锁三、读锁之间不互斥一、ReentrantLock可重入锁 可重入锁ReentrantL...
    99+
    2024-04-02
  • Java多线程并发ReentrantLock怎么使用
    这篇文章主要介绍“Java多线程并发ReentrantLock怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java多线程并发ReentrantLock怎么使用”文章能帮助大家解决问题。背景...
    99+
    2023-07-02
  • 怎么浅谈Java并发编程中的Java内存模型
    这篇文章的内容主要围绕怎么浅谈Java并发编程中的Java内存模型进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!物理计算机并发问题在介绍Java内存...
    99+
    2023-06-17
  • 怎么在Java中使用ReentrantLock实现并发编程
    这篇文章给大家介绍怎么在Java中使用ReentrantLock实现并发编程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、首先看图二、lock()跟踪源码这里对公平锁和非公平锁做了不同实现,由构造方法参数决定是否公...
    99+
    2023-06-15
  • Java并发编程之StampedLock锁怎么应用
    本篇内容介绍了“Java并发编程之StampedLock锁怎么应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!StampedLock:St...
    99+
    2023-06-30
  • Java的并发锁怎么理解
    本篇内容主要讲解“Java的并发锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java的并发锁怎么理解”吧!   Java 中的并发锁大致分为隐式锁...
    99+
    2024-04-02
  • Java中的HTTP并发处理,JavaScript该如何应对?
    随着互联网的不断发展,Web应用程序的性能和响应速度已经成为用户使用Web应用程序时最重要的考虑因素之一。HTTP并发处理是Web应用程序性能优化的一个重要方面,可以提高Web应用程序的性能和响应速度。本文将介绍Java中的HTTP并发处...
    99+
    2023-09-06
    http 并发 javascript
  • 浅谈Java中的atomic包实现原理及应用
    1.同步问题的提出假设我们使用一个双核处理器执行A和B两个线程,核1执行A线程,而核2执行B线程,这两个线程现在都要对名为obj的对象的成员变量i进行加1操作,假设i的初始值为0,理论上两个线程运行后i的值应该变成2,但实际上很有可能结果为...
    99+
    2023-05-30
    java atomic 原理
  • Go语言并发编程中的锁应用
    Go语言是一种开源编程语言,最初由Google开发,旨在提升程序员的效率和系统的性能。Go语言支持并发编程,也就是同时执行多个任务,其中使用锁是一种常见的方式来保证并发安全。在本文中,...
    99+
    2024-04-02
  • 浅析Java中并发工具类的使用
    目录CountDownLatch概述案例原理源码分析CyclicBarrier概述案例源码分析与CountDonwLatch的区别Semaphore概述使用场景案例原理Exchang...
    99+
    2022-12-08
    Java并发工具类使用 Java并发工具类 Java并发
  • 并发编程中如何使用Java中的锁?
    并发编程中如何使用Java中的锁? 在Java中,锁是一种用来控制多个线程访问共享资源的机制。锁可以保证在同一时刻只有一个线程可以访问共享资源,从而避免多个线程同时修改数据导致的数据不一致问题。Java中的锁可以分为两种类型:内置锁和显式锁...
    99+
    2023-08-28
    numy shell 并发
  • 浅谈Java中ArrayList线程不安全怎么办
    ArrayList线程不安全怎么办? 有三种解决方法: 使用对应的 Vector 类,这个类中的所有方法都加上了 synchronized 关键字 就和 HashMap ...
    99+
    2024-04-02
  • Java并发中死锁、活锁和饥饿是什么意思
    解答 死锁是指两个或者两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们将无法推进下去。 如果线程的智力不够, 且都秉承着“谦让”的原则,...
    99+
    2024-04-02
  • java乐观锁怎么应用
    在 Java 中,乐观锁通常通过版本号或时间戳来实现。下面是一种常见的应用乐观锁的方式: 定义一个对象,其中包含要被保护的共享数据...
    99+
    2023-10-25
    java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作