欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

并发编程-ReentrantLock类走读

程序员文章站 2022-05-04 20:54:54
...

背景

并发编程中需要使用锁在进行线程间同步和禁止同时访问共有资源的。下面走读下juc包中的ReentrantLock

血缘关系

ReentrantLock血缘

  • Lock接口类,属于jdk中juc包,锁的基类(不包括synchronize关键字)
  • ReentrantLock,可重入锁,实现了Lock

并发编程-ReentrantLock类走读

Lock的其他派生类血缘

并发编程-ReentrantLock类走读
可以看到,在juc的包中,除了读写锁,就没其他的锁实现Lock了,可见原生的锁种类并不繁多(自旋锁、乐观悲观锁等都是一种概念和方法,在java中并没有特殊的实现类)。

源码走读

Lock接口类

并发编程-ReentrantLock类走读

  • lock,获取锁资源(不可中断)
  • lockInterruptibly,获取锁资源,但是可以在等待中被中断
  • tryLock,尝试获取锁资源,如果获取不到,则返回false
  • unlock,释放锁
  • newCondition,创建同步实例

ReentrantLock类

并发编程-ReentrantLock类走读

  • getHoldCount,查看同时锁住的线程个数
  • isHeldByCurrentThread,是否被当前线程获取锁
  • isLocked,是否被获取
  • isFair,是否是公平锁(构造函数中可以指定)
  • hasQueuedThreads,是否有线程在排队获取锁
  • getQueueLength,获取排队线程数量
  • hasWaiters,指定condition是否有await(需要在lock内使用)
  • getWaitQueueLength,获取await的数量(需要在lock内使用)

使用ReentrantLock需要注意一下几点(之后的实战中也将体现):

  • 线程在lock后的操作时,其他线程不能再lock,这是锁的最基础使用了
  • 使用await和signal需要在lock之后,unlock之前
  • 线程在lock之后,又进入await,那么其他线程可以继续lock,之后的实战将有所体现

实战

isxxx方法实战

public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
    	// false,是否是公平锁
        System.out.println(lock.isFair());
        lock.lock();
        // true,是否已经上锁
        System.out.println(lock.isLocked());
        // true,是否被当前线程上锁
        System.out.println(lock.isHeldByCurrentThread());
        lock.unlock();
    }
}

并发编程-ReentrantLock类走读

holdxxx方法实战

public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
    	// false
        System.out.println(lock.isHeldByCurrentThread());
        lock.lock();
        // 1,重入次数
        System.out.println(lock.getHoldCount());
        // true
        System.out.println(lock.isHeldByCurrentThread());
        lock.unlock();

        lock.lock();
        lock.lock();
        // 2,重入次数
        System.out.println(lock.getHoldCount());
        lock.unlock();
        lock.unlock();
    }
}

queuedxxx方法实战

public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        createThread();
        createThread();
        createThread();
        // 为了能让线程启动完毕,进行10ms的睡眠
        Thread.sleep(10);
        System.out.println(lock.hasQueuedThreads());
        System.out.println(lock.getQueueLength());
    }

    public static void createThread() {
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println("上锁");
                while (true);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("解锁");
            }
        }).start();
    }
}

并发编程-ReentrantLock类走读
结果分析:

  • 创建了三个线程,第一个线程一直在跑while循环,另外两个线程则阻塞在lock.lock()的位置(因为第一个线程一直占用cpu,所以另外两个线程需要等待锁的释放)。
  • hasQueuedThreads,返回结果true,因为后面两个线程在等待
  • getQueueLength,返回结果2,因为一共2个线程在等待。

awaitxxx方法实战

public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        createThread();
        createThread();
        createThread();
        Thread.sleep(10);
        System.out.println(lock.hasQueuedThreads());
        System.out.println(lock.getQueueLength());

        lock.lock();
        System.out.println(lock.hasWaiters(condition));
        System.out.println(lock.getWaitQueueLength(condition));
        condition.signalAll();
        lock.unlock();
    }

    public static void createThread() {
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println("上锁" + Thread.currentThread().getName());
                condition.await();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("解锁" + Thread.currentThread().getName());
            }
        }).start();
    }
}

并发编程-ReentrantLock类走读
结果分析:

  • 开始创建三个线程,但是都阻塞在await上(注意:lock之后进行await,不再占用cpu实现片,其他线程可以再调用lock
  • 没有线程在排队,而是都进入了await的状态
  • hasWaiters,返回结果true
  • getWaitQueueLength,返回结果3,说明先创建的三个线程都在await
  • 调用condition.signalAll(),对三个线程进行**

点评

  • lock可以new多个Condition,进行灵活的控制,相比Object的wait+notify要灵活、安全。
  • 据说在synchronized优化后,同ReentrantLock的效率一样,特意写代码对比了一下,发现ReentrantLock的效率高一些,但是优势不明显
public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("synchronized lock");
        for (int i = 0; i < 20000000; i++) {
            testSynchronizedLock();
        }
        stopWatch.stop();
        stopWatch.start("reentrant lock");
        for (int i = 0; i < 20000000; i++) {
            testReentrantLock();
        }
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
    }

    public static synchronized void testSynchronizedLock() {

    }

    public static void testReentrantLock() {
        lock.lock();
        lock.unlock();
    }
}

并发编程-ReentrantLock类走读