关于前端: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操作。

本文内容到此结束了。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理