ReentrantLock源码分析和使用案例

网友投稿 797 2022-10-21

ReentrantLock源码分析和使用案例

ReentrantLock源码分析和使用案例

源码分析

构造函数

/**     * 初始化的时候默认给了一个不公平锁     */    public ReentrantLock(){        sync = new NonfairSync();    }    /**     * 也可以加参数来初始化指定使用公平锁还是不公平锁     *     */    public ReentrantLock(boolean{        sync = fair ? new FairSync() : new

常用方法

void  lock()   //加锁 void  unlock()  //释放锁tryLock() //仅在调用时锁定未被另一个线程保持的情况下才获取锁定。tryLock(long //如果锁定在给定的时间内没有被另一个线程保持且当前线程没有被中断,则获取这个锁定。boolean isHeldByCurrentThread();   // 当前线程是否保持锁定boolean isLocked()  // 是否存在任意线程持有锁资源void lockInterruptbly()  // 如果当前线程未被中断,则获取锁定;如果已中断,则抛出异常(InterruptedException)int getHoldCount()   // 查询当前线程保持此锁定的个数,即调用lock()方法的次数int getQueueLength()   // 返回正等待获取此锁定的预估线程数int getWaitQueueLength(Condition condition)  // 返回与此锁定相关的约定condition的线程预估数boolean hasQueuedThread(Thread thread)  // 当前线程是否在等待获取锁资源boolean hasQueuedThreads()  // 是否有线程在等待获取锁资源boolean hasWaiters(Condition condition)  // 是否存在指定Condition的线程正在等待锁资源boolean isFair()   // 是否使用的是公平锁

公平锁与非公平锁

static final class FairSync extends Sync{    final void lock(){ // 1 注意对比公平锁和非公平锁的这个方法        acquire(1);    }    public final void acquire(int{        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }    protected final boolean tryAcquire(int{        final Thread current = Thread.currentThread();        int c = getState();        if (c == 0) {            // 2 和非公平锁相比,这里多了一个判断:是否有线程在等待            if (!hasQueuedPredecessors() &&                compareAndSetState(0, acquires)) {                setExclusiveOwnerThread(current);                return true;            }        }        else if (current == getExclusiveOwnerThread()) {            int nextc = c + acquires;            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        return false;    }}static final class NonfairSync extends Sync{    final void lock(){      //  1 和公平锁相比,这里会直接先进行一次CAS,如果当前正好没有线程持有锁,      // 如果成功获取锁就直接返回了,就不用像公平锁那样一定要进行后续判断        if (compareAndSetState(0, 1))            setExclusiveOwnerThread(Thread.currentThread());        else            acquire(1);    }

public final void acquire(int{ if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}protected final boolean tryAcquire(int{ return

}

final boolean nonfairTryAcquire(int{ final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 2 这里没有对阻塞队列进行判断 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } }

else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true;}return false;

}

可以看到公平锁和非公平锁的两个区别: (1) 线程在获取锁调用lock()时,非公平锁首先会进行一次CAS尝试抢锁,如果此时没有线程持有锁或者正好此刻有线程执行完释放了锁(state == 0),那么如果CAS成功则直接占用锁返回。 (2) 如果非公平锁在上一步获取锁失败了,那么就会进入nonfairTryAcquire(int acquires),在该方法里,如果state的值为0,表示当前没有线程占用锁或者刚好有线程释放了锁,那么就会CAS抢锁,如果抢成功了,就直接返回了,不管是不是有其他线程早就到了在阻塞队列中等待锁了。而公平锁在这里抢到锁了,会判断阻塞队列是不是空的,毕竟要公平就要讲先来后到,如果发现阻塞队列不为空,表示队列中早有其他线程在等待了,那么公平锁情况下线程会乖乖排到阻塞队列的末尾。 如果非公平锁 (1)(2) 都失败了,那么剩下的过程就和非公平锁一样了。 (3) 从(1)(2) 可以看出,非公平锁可能导致线程饥饿,但是非公平锁的效率要高。## 使用案例### lock()、unlock()

@Slf4j
@ThreadSafe
public class ReentrantLockExample {

/** * 请求总数 */public static int clientTotal = 5000;public static int count = 0;/** * 定义一个可重入锁 */final static ReentrantLock lock = new ReentrantLock();public static void main(String[] args){ //创建线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //计数器 (把请求计数) final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { executorService.execute(() -> { try { add(); } catch (Exception e) { log.error("exception", e); } //计数器减1 countDownLatch.countDown(); }); } //当所有请求结束 countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count);}private static void add(){ //操作前加锁 lock.lock(); try { count++; } catch (Exception e) { } finally { //操作后在finally中关闭锁,确保锁成功释放,避免死锁

多次输出结果都是5000,证明线程安全。

19:43:01.841 [main] INFO com.zjq.concurrency.lock.ReentrantLockExample - count:5000

### new ReentrantLock(true)公平锁

true)参数为true,表明实现公平锁机制 */final static ReentrantLock lock = new ReentrantLock(true);public static void main(String[] args) throws{ new Thread(() -> testLock(), "线程壹号").start(); new Thread(() -> testLock(), "线程贰号").start(); new Thread(() -> testLock(), "线程叁号").start();}private static void testLock(){ for (int i = 0; i < 2; i++) { //操作前加锁 lock.lock(); try { log.info("{}获取了锁", Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { //操作后在finally中关闭锁,确保锁成功释放,避免死锁

输出结果,两次执行顺序完全一样。

new ReentrantLock()非公平锁

new一个ReentrantLock的时候参数默认为false,可以不用指定为false */final static ReentrantLock lock = new

非公平锁输出没有规律,随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁

lockInterruptbly()响应中断

/**     * 定义一个非公平锁,new一个ReentrantLock的时候参数默认为false,可以不用指定为false     */    final static ReentrantLock lock1 = new ReentrantLock();    final static ReentrantLock lock2 = new ReentrantLock();    public static void main(String[] args) throws{        Thread thread1 = new Thread(new ThreadTest(lock1, lock2), "线程壹号");        Thread thread2 = new Thread(new ThreadTest(lock2, lock1), "线程贰号");        thread1.start();        thread2.start();        thread1.interrupt();    }    static class ThreadTest implements Runnable{        Lock lock111;        Lock lock222;        public ThreadTest(Lock lock111, Lock lock222){            this.lock111 = lock111;            this.lock222 = lock222;        }        @Override        public void run(){            try {                //如果当前线程未被中断,则获取锁定;如果已中断,则抛出异常                lock111.lockInterruptibly();                TimeUnit.MILLISECONDS.sleep(50);                lock222.lockInterruptibly();            } catch (InterruptedException e) {                e.printStackTrace();            } finally {                lock111.unlock();                lock222.unlock();                log.info("{}获取到了锁", Thread.currentThread().getName());            }        }    }

我们定义了两个锁lock1和lock2。然后使用两个线程thread1和thread2构造死锁场景。正常情况下,这两个线程相互等待获取资源而处于死循环状态。但是我们此时thread1中断,另外一个线程就可以获取资源,正常地执行了。 输出结果: [图片上传失败...(image-20c310-1659937925150)]

tryLock()、tryLock(long timeout, TimeUnit unit)

/**     * 定义一个非公平锁,new一个ReentrantLock的时候参数默认为false,可以不用指定为false     */    final static ReentrantLock lock1 = new ReentrantLock();    final static ReentrantLock lock2 = new ReentrantLock();    public static void main(String[] args) throws{        Thread thread1 = new Thread(new ThreadTest(lock1,lock2),"线程壹号");        Thread thread2 = new Thread(new ThreadTest(lock2,lock1),"线程贰号");        thread1.start();        thread2.start();    }    static class ThreadTest implements Runnable{        Lock lock111;        Lock lock222;        public ThreadTest(Lock lock111, Lock lock222){            this.lock111 = lock111;            this.lock222 = lock222;        }        @Override        public void run(){            try {                while (!lock1.tryLock()) {                    TimeUnit.MILLISECONDS.sleep(50);                    log.info("{}尝试获取锁",Thread.currentThread().getName());                }                while (!lock2.tryLock()) {                    TimeUnit.MILLISECONDS.sleep(50);                    log.info("{}尝试获取锁",Thread.currentThread().getName());                }            } catch (InterruptedException e) {                e.printStackTrace();            }finally {                lock111.unlock();                lock222.unlock();                log.info("{}获取到了锁",Thread.currentThread().getName());            }        }    }

输出结果:

22:31:13.316 [线程壹号] INFO com.zjq.lock.ReentrantLockTryLockExample - 线程壹号获取到了锁22:31:13.325 [线程贰号] INFO com.zjq.lock.ReentrantLockTryLockExample - 线程贰号尝试获取锁22:31:13.325

Condition的使用

Condition可以非常灵活的操作线程的唤醒,下面是一个线程等待与唤醒的例子,其中用1234序号标出了日志输出顺序

/**     * 请求总数     */    public static int clientTotal = 5000;    public static int count = 0;    /**     * 定义一个可重入锁     */    final static ReentrantLock lock = new ReentrantLock();    public static void main(String[] args) throws{            //创建一个可重入锁            ReentrantLock reentrantLock = new ReentrantLock();            //创建condition            Condition condition = reentrantLock.newCondition();            //线程1            new Thread(() -> {                try {                    reentrantLock.lock();                    log.info("wait signal"); // 1                    condition.await();                } catch (InterruptedException e) {                    e.printStackTrace();                }                log.info("get signal"); // 4                reentrantLock.unlock();            }).start();            //线程2            new Thread(() -> {                reentrantLock.lock();                log.info("get lock"); // 2                try {                    Thread.sleep(3000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                //发送信号                condition.signalAll();                log.info("send signal"); // 3

输出结果:

图片.png

输出讲解:

线程1调用了reentrantLock.lock(),线程进入AQS等待队列,输出1号log接着调用了awiat方法,线程从AQS队列中移除,锁释放,直接加入condition的等待队列中线程2因为线程1释放了锁,拿到了锁,输出2号log线程2执行condition.signalAll()发送信号,输出3号logcondition队列中线程1的节点接收到信号,从condition队列中拿出来放入到了AQS的等待队列,这时线程1并没有被唤醒。线程2调用unlock释放锁,因为AQS队列中只有线程1,因此AQS释放锁按照从头到尾的顺序,唤醒线程1线程1继续执行,输出4号log,并进行unlock操作。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Gin 框架的文件上传组件
下一篇:Sentinel- 高可用流量管理框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~