ReentrantLock的lock(), tryLock(), tryLock(long timeout, TimeUnit unit), lockInterruptibly() 及使用场景示例

一直在用concurrent包里的东西,最近想研究一下个中细节,先从ReentrantLock提供的集中获取锁的方式开始吧。

1.ReentrantLock简要介绍

简单介绍一下ReentrantLock,可重入锁,互斥锁,提供了fair和unfair两种模式的锁。默认构造函数是unfair的锁,如果初始化时传入true的参数则会返回fair锁。所谓不公平就是在锁可获取时,不用考虑该锁队列是否有其他waiter,直接获取;反之,对于公平锁来讲就是当等待的锁资源可获取时要看下等待队列中当前线程是不是head线程,如果不是则不获取。

简单的看一下获取锁的代码:

//fair lock tryAcquire
if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
//unfair lock nonfairTryAcquire
if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

从上面的代码块可以清晰的看到,公平锁fairLock在获取之前会看下自己是不是没有前继元素。而非公平锁unfairLock并不会。

2.ReentrantLock提供的获取锁的方式

ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()

1)lock()

public void lock() {
        sync.lock();
    }
  • 当锁可用,并且当前线程没有持有该锁,直接获取锁并把count set为1.
  • 当锁可用,并且当前线程已经持有该锁,直接获取锁并把count增加1.
  • 当锁不可用,那么当前线程被阻塞,休眠一直到该锁可以获取,然后把持有count设置为1.

小结:该种方式获取锁不可中断,如果获取不到则一直休眠等待。

2)tryLock() 

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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;
        }
  • 当获取锁时,只有当该锁资源没有被其他线程持有才可以获取到,并且返回true,同时设置持有count为1;
  • 当获取锁时,当前线程已持有该锁,那么锁可用时,返回true,同时设置持有count加1;
  • 当获取锁时,如果其他线程持有该锁,无可用锁资源,直接返回false,这时候线程不用阻塞等待,可以先去做其他事情;
  • 即使该锁是公平锁fairLock,使用tryLock()的方式获取锁也会是非公平的方式,只要获取锁时该锁可用那么就会直接获取并返回true。这种直接插入的特性在一些特定场景是很有用的。但是如果就是想使用公平的方式的话,可以试一试tryLock(0, TimeUnit.SECONDS),几乎跟公平锁没区别,只是会监测中断事件。

3)tryLock(long timeout, TimeUnit unit)

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 从上面代码中可以看出,获取锁成功或者超时之后返回。而且在公平锁和非公平锁的场景下都可以使用,只是会增加对中断事件的监测。
  • 当获取锁时,锁资源在超时时间之内变为可用,并且在等待时没有被中断,那么当前线程成功获取锁,返回true,同时当前线程持有锁的count设置为1.
  • 当获取锁时,在超时时间之内没有锁资源可用,那么当前线程获取失败,不再继续等待,返回false.
  • 当获取锁时,在超时等待时间之内,被中断了,那么抛出InterruptedException,不再继续等待.
  • 当获取锁时,在超时时间之内锁可用,并且当前线程之前已持有该锁,那么成功获取锁,同时持有count加1.

4)lockInterruptibly()

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 当获取锁时,锁资源可用,那么当前线程成功获得锁,同时持有count设置为1,返回true.
  • 当获取锁时,锁资源可用,当前线程已持有该锁,它成功获取该锁,同时持有count增加1,返回true.
  • 当获取锁时,锁资源不可用,那么该线程开始阻塞休眠等待,但是等待过程中如果有中断事件,那么会停止等待,立即返回.
  • 当获取锁时,锁资源不可用,线程开始阻塞休眠等待,如果等待过程中锁资源变为可用,那么当前线程成功获得锁,同时持有count设置为1,返回true.

小结:lockInterruptibly()获取锁是以排他的模式获取,一旦被中断就放弃等待获取。在等待开始时首先检测中断状态,然后至少调用一次tryAcquire,成功获取就返回true。否则当前线程就开始排队,并且不断的被blocking、unblocking、invoking tryAcquire 直到获取成功或者被中断为止。

3.使用场景示例

下面提供三个demo分别使用lock(), tryLock()的方式获取锁资源,示例展示了持续等待,立即返回,限时等待,中断等场景的使用。

1.如果当前获得锁的线程在做大量耗时的工作,使用lock.lock()方法申请锁的线程会一直阻塞,这样就降低了多线程的效率。而使用tryLock()方法申请锁,如果锁不可用则线程不会阻塞,转而可以去做其他工作。代码实例如下:

public class TestLockAndTryLock {
    private ReentrantLock rlock = new ReentrantLock();

    private void lockTest(){
        long currentTime = System.currentTimeMillis();
        try {
            rlock.lock();

            while (System.currentTimeMillis() - currentTime <= 1000){
                //assume do something
            }
            System.out.println("lockTest----current thread get the lock: " + Thread.currentThread().getName());
        }finally {
            rlock.unlock();
            System.out.println("lockTest----current thread release the lock:  " + Thread.currentThread().getName());
        }
    }

    private void tryLockTest(){

        long currentTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - currentTime <= 100){
            //assume do something
        }

        if (rlock.tryLock()){
            try {
                System.out.println("tryLockTest----current thread get the lock: " + Thread.currentThread().getName());

            }finally {
                rlock.unlock();
                System.out.println("tryLockTest----current thread release the lock: " + Thread.currentThread().getName());
            }

        }else {
            System.out.println("tryLockTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args){

        TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

        Thread lockThread = new Thread(
                () -> lockAndTryLock.lockTest(), "Lock-Thread" );

        Thread tryLockThread = new Thread(
                () -> lockAndTryLock.tryLockTest(), "TryLock-Thread" );

        tryLockThread.start();
        lockThread.start();

    }

}
output:

tryLockTest----current thread CAN NOT get the lock: TryLock-Thread
lockTest----current thread get the lock: Lock-Thread
lockTest----current thread release the lock:  Lock-Thread

lock方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前会一直处于 阻塞状态。如果出现死锁,那么lock方法就无法被终止。但是tryLock(long,TimeUnit)在等待超时之后可以结束等待。demo如下:

public class TestLockAndTryLock {
    private ReentrantLock rlock = new ReentrantLock();

    private void lockTest(){
        long currentTime = System.currentTimeMillis();
        try {
            rlock.lock();

            System.out.println("lockTest----current thread get the lock: " + Thread.currentThread().getName());

            while (System.currentTimeMillis() - currentTime <= 5000){
                //assume do something
            }

        }finally {
            rlock.unlock();
            System.out.println("lockTest----current thread release the lock:  " + Thread.currentThread().getName());
        }
    }

    private void tryLockTest(){

        long currentTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - currentTime <= 100){
            //assume do something
        }

        if (rlock.tryLock()){
            try {
                System.out.println("tryLockTest----current thread get the lock: " + Thread.currentThread().getName());


            }finally {
                rlock.unlock();
                System.out.println("tryLockTest----current thread release the lock: " + Thread.currentThread().getName());
            }

        }else {
            System.out.println("tryLockTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
        }
    }


    private void tryLockInterruptTest(){

        long currentTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - currentTime <= 100){
            //assume do something
        }

        try {
            System.out.println("Begin time: " + System.currentTimeMillis());
            if (rlock.tryLock(1, TimeUnit.SECONDS)){
                try {
                    System.out.println("tryLockInterruptTest----current thread get the lock: " + Thread.currentThread().getName());

                }finally {
                    rlock.unlock();
                    System.out.println("tryLockInterruptTest----current thread release the lock: " + Thread.currentThread().getName());
                }

            }else {
                System.out.println("End time: " + System.currentTimeMillis());
                System.out.println("tryLockInterruptTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args){

        TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

        Thread lockThread = new Thread(
                () -> lockAndTryLock.lockTest(), "Lock-Thread" );

        Thread tryLockInterruptThread = new Thread(
                () -> lockAndTryLock.tryLockInterruptTest(), "TryLockInterrupt-Thread"
        );

        tryLockInterruptThread.start();
        lockThread.start();

    }

}
output:
lockTest----current thread get the lock: Lock-Thread
Begin time: 1533636472680
End time: 1533636473681
tryLockInterruptTest----current thread CAN NOT get the lock: TryLockInterrupt-Thread
lockTest----current thread release the lock:  Lock-Thread

同时,tryLock(long, TimeUnit)可以被中断,demo如下:

private void tryLockInterruptTest(){

        long currentTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - currentTime <= 100){
            //assume do something
        }

        try {
            System.out.println("Begin time: " + System.currentTimeMillis());
            if (rlock.tryLock(3, TimeUnit.SECONDS)){
                try {
                    System.out.println("tryLockInterruptTest----current thread get the lock: " + Thread.currentThread().getName());

                }finally {
                    rlock.unlock();
                    System.out.println("tryLockInterruptTest----current thread release the lock: " + Thread.currentThread().getName());
                }

            }else {
                System.out.println("End time: " + System.currentTimeMillis());
                System.out.println("tryLockInterruptTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            System.out.println("tryLockInterruptTest Interrupt----current thread is interrupted: " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args){

        TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

        Thread lockThread = new Thread(
                () -> lockAndTryLock.lockTest(), "Lock-Thread" );

        Thread tryLockInterruptThread = new Thread(
                () -> lockAndTryLock.tryLockInterruptTest(), "TryLockInterrupt-Thread"
        );

        tryLockInterruptThread.start();
        lockThread.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "is interrupted now. ");
        }

        tryLockInterruptThread.interrupt();

    }
output:

lockTest----current thread get the lock: Lock-Thread
Begin time: 1533637530378
tryLockInterruptTest Interrupt----current thread is interrupted: TryLockInterrupt-Thread
lockTest----current thread release the lock:  Lock-Thread

很明显被中断了,没有完成等待。

以上是对ReentrantLock的几种获取锁的方法的详解,并附以demo示例,如有疑问可以留言讨论。

 

refer:https://blog.csdn.net/u011784767/article/details/51659701

你可能感兴趣的:(JAVA基础,多并发)