乐趣区

关于前端:ReentrantLock源码分析和使用案例

源码剖析

构造函数

<pre class="prettyprint hljs cpp">/**
     * 初始化的时候默认给了一个不偏心锁
     */
    public ReentrantLock() {sync = new NonfairSync();
    }

    /**
     * 也能够加参数来初始化指定应用偏心锁还是不偏心锁
     *
     */
    public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

罕用办法

<pre class="prettyprint hljs less">void  lock()   // 加锁 
void  unlock()  // 开释锁
tryLock() // 仅在调用时锁定未被另一个线程放弃的状况下才获取锁定。tryLock(long timeout, TimeUnit unit) // 如果锁定在给定的工夫内没有被另一个线程放弃且以后线程没有被中断,则获取这个锁定。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()   // 是否应用的是偏心锁

偏心锁与非偏心锁

<pre class="prettyprint hljs java">static final class FairSync extends Sync {final void lock() { // 1 留神比照偏心锁和非偏心锁的这个办法
        acquire(1);
    }
    public final void acquire(int arg) {if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();}
    protected final boolean tryAcquire(int acquires) {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 arg) {if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();}
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}

}

final boolean nonfairTryAcquire(int acquires) {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()

<pre class=”prettyprint hljs cs”>@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) throws Exception {
    // 创立线程池
    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 中敞开锁,确保锁胜利开释,防止死锁
        lock.unlock();}
}


屡次输入后果都是 5000,证实线程平安。

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


### new ReentrantLock(true) 偏心锁 
 * 定义一个偏心锁 ReentrantLock(true) 参数为 true,表明实现偏心锁机制
 */
final static ReentrantLock lock = new ReentrantLock(true);

public static void main(String[] args) throws Exception {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 中敞开锁,确保锁胜利开释,防止死锁
            lock.unlock();}
    }
}

输入后果, 两次执行程序齐全一样。

new ReentrantLock() 非偏心锁

 * 定义一个非偏心锁,new 一个 ReentrantLock 的时候参数默认为 false,能够不必指定为 false
 */

final static ReentrantLock lock = new ReentrantLock();

非偏心锁输入没有法则,随机的获取,谁运气好,cpu 工夫片轮到哪个线程,哪个线程就能获取锁

lockInterruptbly() 响应中断

<pre class="prettyprint hljs java">/**
     * 定义一个非偏心锁,new 一个 ReentrantLock 的时候参数默认为 false,能够不必指定为 false
     */
    final static ReentrantLock lock1 = new ReentrantLock();
    final static ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) throws Exception {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)

<pre class="prettyprint hljs java">/**
     * 定义一个非偏心锁,new 一个 ReentrantLock 的时候参数默认为 false,能够不必指定为 false
     */
    final static ReentrantLock lock1 = new ReentrantLock();
    final static ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) throws Exception {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());
            }

        }
    }

输入后果:

<pre class="prettyprint hljs css">22:31:13.316 [线程壹号] INFO com.zjq.lock.ReentrantLockTryLockExample - 线程壹号获取到了锁
22:31:13.325 [线程贰号] INFO com.zjq.lock.ReentrantLockTryLockExample - 线程贰号尝试获取锁
22:31:13.325 [线程贰号] INFO com.zjq.lock.ReentrantLockTryLockExample - 线程贰号获取到了锁

Condition 的应用

Condition 能够非常灵活的操作线程的唤醒,上面是一个线程期待与唤醒的例子,其中用 1234 序号标出了日志输入程序

<pre class="prettyprint hljs cpp">/**
     * 申请总数
     */
    public static int clientTotal = 5000;

    public static int count = 0;
    /**
     * 定义一个可重入锁
     */
    final static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
            // 创立一个可重入锁
            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
                reentrantLock.unlock();}).start();}

输入后果:

输入解说:

  1. 线程 1 调用了 reentrantLock.lock(),线程进入 AQS 期待队列,输入 1 号 log
  2. 接着调用了 awiat 办法,线程从 AQS 队列中移除,锁开释,间接退出 condition 的期待队列中
  3. 线程 2 因为线程 1 开释了锁,拿到了锁,输入 2 号 log
  4. 线程 2 执行 condition.signalAll() 发送信号,输入 3 号 log
  5. condition 队列中线程 1 的节点接管到信号,从 condition 队列中拿进去放入到了 AQS 的期待队列, 这时线程 1 并没有被唤醒。
  6. 线程 2 调用 unlock 开释锁,因为 AQS 队列中只有线程 1,因而 AQS 开释锁依照从头到尾的程序,唤醒线程 1
  7. 线程 1 继续执行,输入 4 号 log,并进行 unlock 操作。

本文内容到此结束了。

退出移动版