使用并理解ReentrantLock

1.ReentrantLock的概念

ReentrantLock可重入[1] 的互斥锁,虽然与synchronized相似,但是会比synchronized更加灵活(具有更多的方法),ReentrantLock底层基于AbstractQueuedSynchronizer (AQS)[2] 实现。在ReentrantLock默认的策略是非公平锁[3]

概念解释:
[1]什么是可重入锁?
synchronized 和 ReentrantLock 都是可重入锁,指的是当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁
[2]什么是AQS?
AbstractQueuedSynchronizer抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题。
[3]公平锁和非公平锁分别是什么?
举个例子:我早上去买早餐,排队的人和我都是有素质的文明人,大家老老实实排队,晚来的到队尾排队,这样大家都觉得很公平,先到先得,所以这是公平锁。非公平锁就是我变成了一个凶残霸道的人,上来就挤到第一位前面,但偶尔会碰到硬茬子,让我滚到后面排队去。

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死

2.synchronized和ReentrantLock的比较:

在这里插入图片描述

2.ReentrantLock的使用

lock()获取锁,unlock()释放锁

传统的synchronized代码:

public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                synchronized ("lock"){
                    System.out.println("线程"+Thread.currentThread().getName()+"获得了锁");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        };

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

        t1.start();
        t2.start();
    }

如果用ReentrantLock替代,可以把代码改造为:

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            //实例化ReentrantLock
            final Lock lock = new ReentrantLock();

            @Override
            public void run() {
                //加锁
                lock.lock();
                System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //释放锁
                lock.unlock();
            }
        };

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

        t1.start();
        t2.start();
    }

3.tryLock()尝试获取锁

和synchronized不同的是,ReentrantLock可以尝试获取锁。
tryLock() : 尝试获取锁,一定时间后,还没有持有锁, 则自动中断

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

        new Thread(()->{
            if (lock1.tryLock()){
                System.out.println("A-lock1");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (lock2.tryLock()){
                    System.out.println("A-lock2");
                }else {
                    System.out.println("A获取锁失败...");
                }
            }
            if (lock2.isHeldByCurrentThread()){lock2.unlock();}
            if (lock1.isHeldByCurrentThread()){lock1.unlock();}
        },"A").start();

        new Thread(()->{
            if (lock2.tryLock()){
                System.out.println("B-lock2");
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (lock1.tryLock()){
                    System.out.println("B-lock1");
                }else {
                    System.out.println("B获取锁失败...");
                }
            }
            if (lock2.isHeldByCurrentThread()){lock2.unlock();}
            if (lock1.isHeldByCurrentThread()){lock1.unlock();}
        },"B").start();
    }

上述代码在尝试获取锁的时候,如果等待一段时间后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去。所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁

4.lockInterruptibly()中断锁

以下代码会出现死锁

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock1 = new ReentrantLock();
        ReentrantLock lock2 = new ReentrantLock();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    lock1.lock();
                    System.out.println("A--lock1");
                    lock2.lock();
                    System.out.println("A--lock2");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (lock2.isHeldByCurrentThread()) lock2.unlock();
                    if (lock1.isHeldByCurrentThread()) lock1.unlock();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    lock2.lock();
                    System.out.println("B--lock2");
                    lock1.lock();
                    System.out.println("B--lock1");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (lock1.isHeldByCurrentThread()) lock1.unlock();
                    if (lock2.isHeldByCurrentThread()) lock2.unlock();
                }
            }
        };
        t1.start();
        t2.start();

但是当线程获取了中断锁的时候, 其他线程就可以中断这把锁

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock1 = new ReentrantLock();
        ReentrantLock lock2 = new ReentrantLock();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    lock1.lockInterruptibly();
                    System.out.println("A--lock1");
                    lock2.lockInterruptibly();
                    System.out.println("A--lock2");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (lock2.isHeldByCurrentThread()) lock2.unlock();
                    if (lock1.isHeldByCurrentThread()) lock1.unlock();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    lock2.lockInterruptibly();
                    System.out.println("B--lock2");
                    lock1.lockInterruptibly();
                    System.out.println("B--lock1");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (lock1.isHeldByCurrentThread()) lock1.unlock();
                    if (lock2.isHeldByCurrentThread()) lock2.unlock();
                }
            }
        };
        t1.start();
        t2.start();
        Thread.sleep(5000);
        t1.interrupt();
    }

5.源码简单理解

查看JDK的源码(JDk1.8),可以看出ReentrantLock默认是非公平锁,如果在构造方法当中传入TRUE,则使用公平锁。

    public ReentrantLock() {
        sync = new NonfairSync();
    }
	...........
	...........
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

而如果继续观察NonfairSync()FairSync(),则会发现NonfairSyncFairSync都继承自Sync

    static final class NonfairSync extends Sync {
        ............
        ............
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
		...........................
		............................

    static final class FairSync extends Sync {
        ............
        ............
        final void lock() {
            acquire(1);
        }

       ····················
       ····················

Sync继承自AQS

    abstract static class Sync extends AbstractQueuedSynchronizer {
       ..........
       ...........
        abstract void lock();
        .........
        .........
        .........

通过分析源码,可知对ReentrantLock的操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对 AQS的操作。如将ReentrantLocklock()转化为对 Synclock()的调用,而具体会根据采用的是NonfairSync还是FairSync而调用不同的对lock()实现。

    public void lock() {
        sync.lock();
    }

unlock()则是调用的release(),这是AQS当中的方法

    public void unlock() {
        sync.release(1);
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值