并发编程——重入锁ReentrantLock

目录

显式地同步功能——锁

synchronized和ReentrantLock的区别

ReentrantLock实现原理

ReentrantLock公平锁和非公平锁的差异

ReentrantLock时序图

独占式同步状态获取流程


显式地同步功能——锁

Java SE 5之后,并发包中新增 了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。

一个简单的Lock使用方式的示例:

// ReentrantLock实现简单的独占锁
public class LockDemo {
    private Lock lock = new ReentrantLock();
    private int count = 0;
    public void doSth () {
        lock.lock();
        try {
            Thread.sleep(1);
            count++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final LockDemo lockDemo = new LockDemo();
        
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{lockDemo.doSth();}).start();
        }
​
        Thread.sleep(3000);
        System.out.println("result:"+lockDemo.count);
    }
}

ReentrantLock是独占锁,就是能够防止多个线程同时访问共享资源的锁。当然,也有共享锁,比如ReadWriteLock,这种读写锁的实现允许多个线程同时读取数据,但只允许一个线程写入数据。这在读多写少的场景中可以提高性能和吞吐量。

public class RwLockDemo {
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map cacheMap = new HashMap<>();
    private Lock read = readWriteLock.readLock();
    private Lock write = readWriteLock.writeLock();
​
   public Object get(String key) {
       System.out.println("start read ...");
       read.lock();
       try {
           return cacheMap.get(key);
       } finally {
           read.unlock();
       }
   }
​
   public Object put(String key,Integer value){
       System.out.println("start write ...");
       write.lock();
       try {
           return cacheMap.put(key, value);
       } finally {
           write.unlock();
       }
   }
​
   public static void main(String[] args) throws InterruptedException {
       final RwLockDemo rwLockDemo = new RwLockDemo();
       rwLockDemo.put("a",1);
       rwLockDemo.put("b",2);
       System.out.println(rwLockDemo.cacheMap.get("a"));
   }
}
synchronized和ReentrantLock的区别
  • 锁的实现:synchronized是java语言的关键字,基于JVM实现。ReentrantLock基于JDK的API实现的(lock()和unlock()配合finally语句块实现的。)

  • 性能:JDK1.6以前,synchronized重量级锁性能略差;1.6以后加入自适应自旋、锁消除等,两者性能就差不多了。

  • 功能特点:ReentrantLock提供了些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。

区别 synchronized ReentrantLock
锁实现机制 对象头监视器模式 依赖AQS
灵活性 不灵活 支持响应中断、超时、尝试获取锁
释放锁形式 自动释放锁 显式调用unlock()
支持锁类型 非公平锁 公平锁和非公平锁
条件队列 单条件队列 多条件队列
可重入支持 支持 支持
ReentrantLock实现原理

ReentrantLock的一些关键实现原理:

  1. CAS操作ReentrantLock的实现依赖于CAS(Compare-And-Swap)操作,这是一种原子操作,用于在多线程环境中修改共享变量。CAS操作允许一个线程比较内存中的值与预期值,如果相等,则更新为新的值。

  2. 锁状态ReentrantLock内部维护了一个锁状态(lock state),这个状态可以告诉锁当前是被哪个线程占用的,以及该线程占用了几次锁。这允许同一个线程多次获取同一把锁而不会发生死锁。当一个线程首次获取锁时,锁状态设置为1,每次再次获取锁时,状态递增。

  3. AQS(AbstractQueuedSynchronizer)ReentrantLock的实现基于AQS,这是一个用于构建锁和其他同步器的抽象框架。AQS提供了队列、状态管理、等待线程管理等功能。ReentrantLock通过扩展AQS并根据自身需求实现其中的一些方法来实现锁的功能。

  4. 公平性和非公平性ReentrantLock支持公平性和非公平性。在公平模式下,等待线程按照FIFO顺序获取锁。在非公平模式下,等待线程有可能插队,当锁释放时,可能不是等待时间最长的线程获得锁。这个选择可以通过ReentrantLock的构造函数来指定。

  5. 可中断性ReentrantLock允许等待锁的线程响应中断信号,通过使用lockInterruptibly()方法可以实现这一点。

  6. 条件变量ReentrantLock还提供了条件变量(Condition),允许线程在某些条件下等待和唤醒。条件变量是ReentrantLock的一部分,可以通过newCondition()方法创建。

ReentrantLock公平锁和非公平锁的差异

ReentrantLock可以用于实现公平锁和非公平锁,它们的主要区别在于线程获取锁的顺序和策略。

  1. 公平锁(Fair Lock)

    • 公平锁确保线程按照请求锁的顺序获得锁资源,即按照FIFO(先进先出)的顺序分配锁。

    • 当一个线程请求锁但锁已经被其他线程占用时,该线程会进入等待队列,并按照请求锁的顺序排队等待。

    • 当锁释放时,等待队列中的线程中最早请求锁的线程将获得锁,以确保公平性。

    • 优点:公平锁能够避免线程饥饿,即某个线程无限期地等待获取锁的情况。每个线程最终都有机会获取锁。

    • 缺点:因为需要维护等待队列和按照顺序分配锁,公平锁的性能可能相对较差,特别是在高并发情况下。

    • 使用方式:通过在创建 ReentrantLock 实例时传递 true 参数来创建公平锁,例如:ReentrantLock fairLock = new ReentrantLock(true);

  2. 非公平锁(Non-Fair Lock)

    • 非公平锁不考虑线程请求锁的顺序,当锁可用时,任何等待的线程都有机会立即获得锁。

    • 当锁被释放时,如果有等待的线程,JVM会从等待线程中随机选择一个线程来获得锁。

    • 优点:非公平锁的性能通常比公平锁更好,因为它减少了维护等待队列和按照顺序分配锁的开销。

    • 缺点:非公平锁可能导致某些线程一直无法获取锁,因为它们不会按照请求锁的顺序获得锁,可能会导致线程饥饿的情况。

    • 使用方式:默认情况下,ReentrantLock 创建的是非公平锁,可以不传递参数或传递 false 来明确使用非公平锁。

选择公平锁还是非公平锁取决于应用的需求和性能考虑。如果你需要确保线程以公平的方式获得锁资源,可以使用公平锁。如果性能更为重要,而且你不担心某些线程可能被饿死,那么非公平锁可能是更好的选择。一般来说,非公平锁在高并发场景下表现更好,但可能会导致某些线程长时间等待。

ReentrantLock时序图

并发编程——重入锁ReentrantLock_第1张图片

  • 入口:ReentrantLock.java中的lock()方法调用了继承自AbstractQueuedSynchronizer的抽象静态类Sync的lock()方法,默认情况下创建的是非公平锁。(部分源码)

    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {}
    public void lock() {
            sync.lock();
        }
  • 获取同步状态:非公平锁直接CAS尝试抢占,如果锁没有被抢占,直接抢占成功;大部分时候是抢占失败的,调用acquire()独占式获取同步状态会调用继承自AQS进行重写的acquire()判断tryAcquire()和addWaiter。

    public final void acquire(int arg) 
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 判断结果,同步失败,线程进入同步队列挂起状态等待

独占式同步状态获取流程

就是acquire(int arg)方法调用流程。如下所示:

并发编程——重入锁ReentrantLock_第2张图片

你可能感兴趣的:(并发编程,java,算法,开发语言)