JUC锁框架——重入锁ReentrantLock

北城以北 提交于 2019-11-27 16:10:06

重入锁(ReentrantLock)

  ReentrantLock是一种可重入的互斥锁,并且加锁是一种显式操作,对逻辑控制的灵活性远远大于synchronized关键字。重入锁是可以完全替代synchronized。并且重入锁的性能是远高于synchronized的,但是jdk6.0开始,jdk对synchronized做了大量的优化,使得两者性能差距不大。另外,ReentrantLock可结合Condition、以及提供了中断响应、锁申请等待限时、公平锁等。

公平锁、非公平锁(ReentrantLock)

  ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。

 运行如下代码,对比公平锁和非公平锁的运行结果

    public static void main(String[] args) throws InterruptedException {
        Lock noFairReentrantLock = new ReentrantLock();
        Lock fairReentrantLock = new ReentrantLock(true);

        Thread[] threads = new Thread[10];
        for(int i=0;i<10;i++){
            threads[i] = new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"  [start]");
                fairReentrantLock.lock();//也可以切换为非公平锁,观察运行结果
                try {
                    System.out.println(Thread.currentThread().getName()+"  [获得锁]");
                }finally {
                    fairReentrantLock.unlock();
                }
            });
        }
        for(int i=0;i<5;i++){
            threads[i].start();
        }
        for(int i=0;i<5;i++){
            threads[i].join();
        }
    }

}

重入锁的中断响应功能

对于synchronized块来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样的情况。比如,一个正在等待获取锁的线程被“告知”无须继续等待下去,就可以停止工作了。

下面我们看一个利用中断响应功能解决的一个死锁问题

public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock1 = new ReentrantLock();
        ReentrantLock lock2 = new ReentrantLock();

        //线程t1和t2构成了死锁,此时我们可以中断的方式解决死锁问题
        Runnable runnable = ()->{
            try {
                if(Thread.currentThread().getName().equals("t1")){
                    lock1.lockInterruptibly(); //在加锁的过程中仍然可以相应中断
                    Thread.sleep(100);
                    lock2.lockInterruptibly();
                }else{
                    lock2.lockInterruptibly();
                    Thread.sleep(100);
                    lock1.lockInterruptibly();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (lock1.isHeldByCurrentThread()) lock1.unlock();
                if (lock2.isHeldByCurrentThread()) lock2.unlock();
            }
        };

        Thread t1 = new Thread(runnable,"t1");
        Thread t2 = new Thread(runnable,"t2");

        t1.start();t2.start();
        Thread.sleep(1000);
        //以中断的方式解决死锁问题
        t2.interrupt();
    }

锁申请等待限时

可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。

  • tryLock(),线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回false,也就是不会使当前线程等待,所以不会产生死锁。
  • tryLock(long timeout, TimeUtil unit),表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。
public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Runnable runnable = ()->{
            try {
                if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒
                    Thread.sleep(2000);
                } else {
                    System.err.println(Thread.currentThread().getName() + "获取锁失败!");
                }
            } catch (Exception e) {
                if (lock.isHeldByCurrentThread()) lock.unlock();
            }
        };
        Thread t1 = new Thread(runnable,"t1");
        Thread t2 = new Thread(runnable,"t2");
        t1.start();t2.start();
    }

ReentrantLock 配合 Condition 使用

配合关键字synchronized使用的方法如:await()、notify()、notifyAll(),同样配合ReentrantLock 使用的Conditon提供了以下方法:

public interface Condition {
    void await() throws InterruptedException; // 类似于Object.wait()
    void awaitUninterruptibly(); // 与await()相同,但不会再等待过程中响应中断
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal(); // 类似于Obejct.notify()
    void signalAll();
}

ReentrantLock 配合 Condition的例子

public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(true);
        Condition condition = lock.newCondition();
        Thread t = new Thread(()->{
            try {
                lock.lock();
                System.err.println(Thread.currentThread().getName() + "-线程开始等待...");
                condition.await();
                System.err.println(Thread.currentThread().getName() + "-线程继续进行了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1");
        t.start();
        Thread.sleep(1000);
        System.err.println("过了1秒后...");
        lock.lock();
        condition.signal(); // 调用该方法前需要获取到创建该对象的锁否则会产生java.lang.IllegalMonitorStateException异常
        lock.unlock();
    }

ReentrantLock函数列表

// 创建一个 ReentrantLock ,默认是“非公平锁”。
ReentrantLock()
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
// 查询当前线程保持此锁的次数。
int getHoldCount()
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正等待获取此锁的线程估计数。
int getQueueLength()
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
// 查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果是“公平锁”返回true,否则返回false。
boolean isFair()
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
// 查询此锁是否由任意线程保持。
boolean isLocked()
// 获取锁。
void lock()
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
// 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
// 试图释放此锁。
void unlock()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!