Java 重入锁 ReentrantLock使用实战

1.简述

ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”。
其可以完全替代 synchronized 关键字。JDK 1.5.0 引入的,其性能远好于 synchronized,但 JDK 1.6.0 开始,JDK 对 synchronized 做了大量的优化,使得两者差距并不大。但其提供了超出synchonized的其他高级功能(例如,中断锁等候、条件变量等),并且使用ReentrantLock比synchronized能获得更好的可伸缩性。也更灵活。

2.特性

  • “独占”,指在同一时刻只能有一个线程获取到锁,而其它获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后续的线程才能够获取锁。
  • “可重入”,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
  • “选择性”,支持获取锁时的公平和非公平性选择。“公平” 指不同的线程获取锁的机制是公平的”,而“不公平” 指不同的线程获取锁的机制是非公平的”。

3.ReentrantLock常用方法

3.1 方法介绍

序号 方法名 返回值 含义
1 lock() void 当前线程-加锁
2 unlock() void 当前线程-解锁
3 tryLock() Boolean 尝试获得锁,仅在调用时锁未被线程占用,获得锁
4 tryLock(long timeout, TimeUnit unit) Boolean 限时的锁等待,一定时间内若扔未获得则返回false
5 isLocked Boolean 是否锁定
6 isFair() Boolean 是否公平
7 lockInterruptibly() Void 如果当前线程未被中断,获取锁
8 isHeldByCurrentThread() Boolean 当前线程是否保持锁锁定,线程的执行lock方法的前后分别是false和true
9 hasQueuedThreads() Boolean 是否有线程等待此锁
10 getHoldCount() int 当前线程保持此锁的次数,即执行此线程执行lock方法的次数
11 getQueueLength() int 返回正等待获取此锁的线程估计数,例如:启动10个线程,1个线程获得锁,此时返回的是9
12 getWaitQueueLength(Condition condition) i 返回等待与此锁相关的给定条件的线程估计数。比如10个线程,用同一个condition对象,并且此时这10个线程都执行了condition对象的await方法,那么此时执行此方法返回10
13 hasWaiters(Condition condition) Boolean 查询是否有线程等待与此锁有关的给定条件(condition),对于指定contidion对象,有多少线程执行了condition.await方法

3.2 tryLock和lock和lockInterruptibly的区别

  • tryLock 能获得锁就返回true,不能就立即返回false
  • tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回false
  • lock 能获得锁就返回true,不能的话一直等待获得锁
  • lock和lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,前者不会抛出异常,而后者会抛出异常

4.使用实例

注:在实例中使用了int i ++操作作为延时代码,i++是非线程安全的,
详细了解可参考
https://blog.csdn.net/qq_38011415/article/details/88857201

4.1 lock与unlock

标准使用格式示例

//创建锁
private static ReentrantLock syncLock = new ReentrantLock();

public void test() {
       try {
                syncLock.lock();
                 // todo 
            } finally {
                //解锁,勿忘,防止死锁
                syncLock.unlock();
            }
    }

测试代码实例

    public static void main(String[] args) throws Exception {
        //创建线程池
        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
        for (int i = 0; i < 10; i++) {
            scheduler.execute(() -> {
                long start = System.currentTimeMillis();
                //尝试加锁
                if (syncLock.tryLock()) {
                    try {
                        long end = System.currentTimeMillis();
                        System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!" + "等待毫秒数:" + (end - start));
                        // 模拟业务代码 耗时
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //解锁
                        syncLock.unlock();
                    }
                } else {
                    long end = System.currentTimeMillis();
                    System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!" + "等待毫秒数:" + (end - start));
                }
            });
        }
        scheduler.shutdown();
        //保证前面的线程都执行完
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

    }

测试结果

4.1 trylock

测试实例1
注:trylock 获取不到锁立马返回

package com.example.reent.lock;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author 码农猿
 */
public class ReentrantLockTest2 {
    private static ReentrantLock syncLock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        //创建线程池
        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
        for (int i = 0; i < 10; i++) {
            scheduler.execute(() -> {
                //加锁
                if (syncLock.tryLock()) {
                    try {

                        for (int j = 0; j < 10; j++) {
                            System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!");
                        }
                        // 模拟业务代码 耗时
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //解锁
                        syncLock.unlock();
                    }
                } else {
                    System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!");

                }
            });
        }
        scheduler.shutdown();
        //保证前面的线程都执行完
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }  
    }
}

测试结果

测试实例12
注:trylock(long timeout, TimeUnit unit) 设置获取锁超时时间

public static void main(String[] args) throws Exception {
        //创建线程池
        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
        for (int i = 0; i < 10; i++) {
            scheduler.execute(() -> {
                long start = System.currentTimeMillis();
                try {
                    //等待 205毫秒尝试加锁
                    if (syncLock.tryLock(205, TimeUnit.MILLISECONDS)) {
                        try {
                            long end = System.currentTimeMillis();
                            System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!" + "等待毫秒数:" + (end - start));
                            // 模拟业务代码 耗时
                            Thread.sleep(100);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            //解锁
                            syncLock.unlock();
                        }
                    } else {
                        long end = System.currentTimeMillis();
                        System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!" + "等待毫秒数:" + (end - start));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        scheduler.shutdown();
        //保证前面的线程都执行完
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
    }

结果图示

总结

优点:
1.加锁和释放锁独立,可以分开来控制。
2.tryLock的方法可以尝试加锁,不会像sychnorized一致阻塞。
3.实现了公平锁和非公平锁。sychnorized只能是非公平锁。

缺点:
1.增加了代码的复杂度。
2.相比sychnorized的自动加锁和释放锁,Lock需要手动,容易忘记,从而出现重大的隐患。

猜你喜欢

转载自blog.csdn.net/qq_38011415/article/details/89057444