java ReentrantLock并发锁使用详解

Serafina ·
更新时间:2024-09-21
· 881 次阅读

目录

一、ReentrantLock是什么

1-1、ReentrantLock和synchronized区别

1-2、ReentrantLock的使用

1-2-1、ReentrantLock同步执行,类似synchronized

1-2-2、可重入锁

1-2-3、锁中断

1-2-4、获得锁超时失败

1-2-5、公平锁

一、ReentrantLock是什么

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

相对于 synchronized, ReentrantLock具备如下特点:

可中断

可以设置超时时间

可以设置为公平锁

支持多个条件变量

与 synchronized 一样,都支持可重入

进入源码可以看到,其实现了公平锁和非公平锁

内部实现了加锁的操作,并且支持重入锁。不用我们再重写

解锁操作

1-1、ReentrantLock和synchronized区别

synchronized和ReentrantLock的区别:

synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;

synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;

synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;

synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;

在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;

ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;

synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;

1-2、ReentrantLock的使用 1-2-1、ReentrantLock同步执行,类似synchronized

使用ReentrantLock需要注意的是:一定要在finally中进行解锁,方式业务抛出异常,无法解锁

public class ReentrantLockDemo { private static int sum = 0; private static ReentrantLock lock=new ReentrantLock(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; i++) { Thread thread = new Thread(()->{ //加锁 lock.lock(); try { // 临界区代码 // TODO 业务逻辑:读写操作不能保证线程安全 for (int j = 0; j < 10000; j++) { sum++; } } finally { // 解锁--一定要在finally中解锁,防止业务代码异常,无法释放锁 lock.unlock(); } }); thread.start(); } Thread.sleep(2000); System.out.println(sum); } }

测试结果:

1-2-2、可重入锁

可重入锁就是 A(加锁)-->调用--->B(加锁)-->调用-->C(加锁),从A到C即使B/C都有加锁,也可以进入

@Slf4j public class ReentrantLockDemo2 { public static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { method1(); } public static void method1() { lock.lock(); try { log.debug("execute method1"); method2(); } finally { lock.unlock(); } } public static void method2() { lock.lock(); try { log.debug("execute method2"); method3(); } finally { lock.unlock(); } } public static void method3() { lock.lock(); try { log.debug("execute method3"); } finally { lock.unlock(); } } }

执行结果:

1-2-3、锁中断

可以使用lockInterruptibly来进行锁中断

lockInterruptibly()方法能够中断等待获取锁的线程。当两个线程同时通过lock.lockInterruptibly()获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

public class ReentrantLockDemo3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("t1启动..."); try { lock.lockInterruptibly(); try { log.debug("t1获得了锁"); } finally { lock.unlock(); } } catch (InterruptedException e) { e.printStackTrace(); log.debug("t1等锁的过程中被中断"); } }, "t1"); lock.lock(); try { log.debug("main线程获得了锁"); t1.start(); //先让线程t1执行 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t1.interrupt(); log.debug("线程t1执行中断"); } finally { lock.unlock(); } } }

执行结果:

1-2-4、获得锁超时失败

可以让线程等待指定的时间,如果还未获取锁则进行失败处理。

如下代码,首先让主线程获得锁,然后让子线程启动尝试获取锁,但是由于主线程获取锁之后,让线程等待了2秒,而子线程获得锁的超时时间只有1秒,如果未获得锁,则进行return失败处理

public class ReentrantLockDemo4 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("t1启动..."); //超时 try { if (!lock.tryLock(1, TimeUnit.SECONDS)) { log.debug("等待 1s 后获取锁失败,返回"); return; } } catch (InterruptedException e) { e.printStackTrace(); return; } try { log.debug("t1获得了锁"); } finally { lock.unlock(); } }, "t1"); lock.lock(); try { log.debug("main线程获得了锁"); t1.start(); //先让线程t1执行 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } }

执行结果:

1-2-5、公平锁

ReentrantLock 默认是不公平的

首先启动500次for循环创建500个线程,然后进行加锁操作,并同时启动了。这样这500个线程就依次排队等待加锁的处理

下面500个线程也是等待加锁操作

如果使用公平锁,下面500的线程只有等上面500个线程运行完成之后才能获得锁。

@Slf4j public class ReentrantLockDemo5 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(true); //公平锁 for (int i = 0; i < 500; i++) { new Thread(() -> { lock.lock(); try { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(Thread.currentThread().getName() + " running..."); } finally { lock.unlock(); } }, "t" + i).start(); } // 1s 之后去争抢锁 Thread.sleep(1000); for (int i = 0; i < 500; i++) { new Thread(() -> { lock.lock(); try { log.debug(Thread.currentThread().getName() + " running..."); } finally { lock.unlock(); } }, "强行插入" + i).start(); } } }

测试结果(后进入的线程都在等待排队)

使用非公平锁的情况下,就可以看到下面500线程有些线程就可以抢占锁了

那ReentrantLock为什么默认使用非公平锁呢?实际上就是为了提高性能,如果使用公平锁,当前锁对象释放之后,还需要去队列中获取第一个排队的线程,然后进行加锁处理。而非公平锁,可能再当前对象释放锁之后,正好有新的线程在获取锁,这样就可以直接进行加锁操作,不必再去队列中读取。

以上就是java ReentrantLock并发锁使用详解的详细内容,更多关于java ReentrantLock并发锁的资料请关注软件开发网其它相关文章!



reentrantlock JAVA

需要 登录 后方可回复, 如果你还没有账号请 注册新账号