juc中的锁

JUC总体结构

JUC(Java.util.concurrent)包结构如下:

  1. 并发集合(Concurrent Collections):这些类提供了线程安全的集合类,用于在多线程环境下进行并发操作。
    • ConcurrentHashMap:线程安全的哈希表实现。
    • ConcurrentLinkedDeque:线程安全的双端队列实现。
    • ConcurrentLinkedQueue:线程安全的队列实现。
    • ConcurrentSkipListMap:线程安全的跳表实现的有序映射表。
    • ConcurrentSkipListSet:线程安全的跳表实现的有序集合。
    • CopyOnWriteArrayList:线程安全的动态数组实现。
    • CopyOnWriteArraySet:线程安全的动态数组实现的集合。
  2. 同步器(Synchronizers):这些类提供了用于控制多个线程之间的协调和同步的工具。
    • CountDownLatch:一个线程等待其他线程完成一定数量的操作后再继续执行。
    • CyclicBarrier:一组线程互相等待,直到所有线程都达到一个屏障点后再继续执行。
    • Semaphore:控制同时访问某个资源的线程数量。
    • Exchanger:两个线程之间进行数据交换。
  3. 原子类(Atomic Variables):这些类提供了原子操作,以确保在多线程环境下的数据安全性。
    • AtomicInteger:原子操作的int类型。
    • AtomicLong:原子操作的long类型。
    • AtomicBoolean:原子操作的boolean类型。
    • AtomicReference:原子操作的引用类型。
  4. 锁框架(Lock Framework):这些类提供了更灵活和可扩展的锁机制。
    • Lock:一个互斥锁的抽象。
    • ReentrantLock:可重入锁的实现。
    • ReadWriteLock:读写锁的抽象。
    • ReentrantReadWriteLock:读写锁的实现。
  5. 带有执行结果的线程:可用于异步回调。
    • Callable
    • CompletableFuture
    • Future
  6. 线程池(Executor Framework):这些类提供了管理和调度线程池的功能。
    • Executor:执行任务的相关接口。
    • ExecutorService:扩展了Executor接口,支持生命周期管理和返回Future对象。
    • ThreadPoolExecutor:线程池的实现。
    • ScheduledExecutorService:支持任务调度的扩展接口。
  7. 定时任务(Scheduled Tasks):这些类用于执行定时任务和周期性任务。
    • ScheduledExecutorService:支持任务调度的扩展接口。
    • ScheduledThreadPoolExecutor:定时任务的实现。
  8. 并发类的辅助工具:这些类提供了一些辅助功能,用于帮助并发编程。
    • Phaser:一个栅栏,用于同步多个线程的执行。
    • Exchanger:两个线程之间进行数据交换。
    • CompletionService:将任务的执行结果异步返回。
  9. 原子数组(Atomic Arrays):这些类提供了原子操作的数组类型。
    • AtomicIntegerArray:原子操作的int数组。
    • AtomicLongArray:原子操作的long数组。
    • AtomicReferenceArray:原子操作的引用类型数组。
  10. 可见性(Visibility):这些类和接口提供了一些可见性的保证。
  • volatile关键字:用于确保变量的可见性。
  • AtomicBoolean:原子操作的boolean类型。

锁简介

锁在Java线程中的作用是用于控制多个线程对共享资源的访问。在多线程环境下,当多个线程同时访问共享资源时,可能会导致数据不一致或竞态条件的问题。锁的使用可以确保在同一时间只有一个线程能够访问共享资源,从而避免并发访问引起的问题。

在JUC(Java.util.concurrent)包中,Lock接口有几个常用的实现类,它们分别是:

  1. ReentrantLock:可重入锁。
    • ReentrantLock是Lock接口的主要实现类,它提供了与synchronized关键字类似的功能,但更加灵活和可扩展。
    • ReentrantLock支持可重入性,即同一个线程可以多次获取锁。
    • ReentrantLock还提供了各种高级功能,如公平性选择、条件变量等。
  2. ReentrantReadWriteLock:读写锁。
    • ReentrantReadWriteLock是Lock接口的另一个实现类,它提供了读写分离的锁机制。
    • ReentrantReadWriteLock允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。
    • 读锁是共享锁,写锁是独占锁。
  3. StampedLock:戳锁。
    • StampedLock是JDK8引入的新的锁机制,它提供了一种乐观读锁的机制。
    • StampedLock允许读锁和写锁之间的转换,提供更高的并发性能。

这些Lock的实现类提供了更灵活、更高级的锁机制,可以替代传统的synchronized关键字。通过使用这些锁,我们可以更好地控制线程的访问和并发操作,确保多线程环境下的数据安全性和性能优化。选择使用哪种锁取决于具体的需求和场景。

传统synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

  3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

  4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类对象。需要注意的是,当synchronized修饰一个类时,它只是获取了该类的类锁,并不会对该类的实例对象产生影响。

    class ClassName {
       public void method() {
          synchronized(ClassName.class) {
             //todo
          }
       }
    }
    

注意点:

1、在定义接口方法时不能使用synchronized关键字。

2、构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

3、synchronized关键字不能继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。

synchronized关键字如何实现可重入

synchronized 关键字在 Java 中实现可重入的机制。可重入是指同一个线程可以多次获取同一个锁,而不会造成死锁或其他问题。

当一个线程再次获得已经被它自己持有的锁时,synchronized 会自动记录该线程已经进入了多少次该锁,并在解锁时适当地减少计数。只有当线程完全释放了该锁,即计数器归零时,其他线程才能获取到该锁。

以下是一个示例代码,演示了 synchronized 关键字的可重入性:

public class SynchronizedExample {
    public synchronized void method1() {
        System.out.println("method1");
        method2();
    }

    public synchronized void method2() {
        System.out.println("method2");
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();
        example.method1();
    }
}

synchronized关键字如何实现条件变量

synchronized 关键字在 Java 中主要用于实现互斥锁(即一次只允许一个线程访问被 synchronized 修饰的代码块或方法),并没有直接提供条件变量的功能。不过,可以通过一些技巧来实现条件变量的效果。

常见的方法是在 synchronized 代码块内使用 wait()、notify() 和 notifyAll() 方法来实现条件变量的等待和通知机制。具体步骤如下:

  • 使用 synchronized 关键字修饰共享资源的代码块或方法,确保同一时刻只有一个线程能够访问这段代码。
  • 在等待条件的线程中使用 wait() 方法将线程置于等待状态,同时释放锁。
  • 在满足某个条件的时候,通过 notify()notifyAll() 方法来唤醒等待的线程。
  • 被唤醒的线程重新获取锁并继续执行。

下面介绍JUC中的锁

ReentrantLock

1.原理

ReentrantLock的实现原理主要包括以下几点:

红色为特殊特性,由AQS继承而来

  1. 状态维护:ReentrantLock内部维护了一个state变量,表示当前锁的状态。当线程首次获取锁时,state会被设置为1,表示线程持有锁。当同一个线程再次获取锁时,state会递增。当线程释放锁时,state递减。只有当state为0时,表示锁没有被任何线程持有。

  2. 获取锁:当一个线程尝试获取锁时,首先会判断state是否为0。如果是0,则表示锁没有被任何线程持有,当前线程可以获取锁,并将state设置为1。如果state不为0,则表示锁已经被其他线程持有。在这种情况下,线程会被阻塞,直到锁被释放,或者当前线程被中断。

  3. 可重入性:ReentrantLock支持线程的可重入性。ReentrantLock内部维护了一个owner线程和一个lock count变量。当一个线程第一次获取锁时,lock count递增,然后线程可以多次获取锁,每次获取锁时lock count都会递增。当线程释放锁时,lock count递减,直到lock count减为0时,锁完全释放,其他线程才能获取该锁

  4. 等待队列:公平锁维护了一个等待队列,用于存放等待获取锁的线程。当一个线程尝试获取锁时,如果锁已经被其他线程持有,那么线程会被放入等待队列中。

    公平状态下:等待队列是一个FIFO(先进先出)队列,即先到先得。
    非公平状态下:等待队列是一个无序队列。
    AQS的队列是从队尾开始放元素,也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。
    
  5. 公平锁:如果构造ReentrantLock时指定了公平锁(通过ReentrantLock(true)),线程会被放入等待队列中,按照先到先得的顺序等待获取锁。当锁释放时,等待队列中的下一个线程会被唤醒并尝试获取锁

  6. 非公平锁:如果构造ReentrantLock时指定了非公平锁(通过ReentrantLock(false),或者使用默认构造函数),线程会直接尝试获取锁,而不会进入等待队列。如果获取锁失败,则会采用一定的策略进行重试,可能会插队获取锁。

  7. 锁的释放:当一个线程释放锁时,会将state递减。如果state为0,则表示锁已经完全释放,其他等待获取锁的线程有机会获取锁。

AQS

AQS是指AbstractQueuedSynchronizer抽象类,它是Java并发包中用于实现同步器的基础框架,提供了一种同步机制,可用于构建各种同步类,如ReentrantLock、CountDownLatch、Semaphore等。

AQS的主要作用是提供了一种基于FIFO(先进先出)等待队列的同步机制,通过内置的同步状态(state)和等待队列来实现线程的等待/唤醒机制。

ReentrantLock是通过继承AQS来实现可重入锁公平锁的。AQSReentrantLock提供了支持,通过继承和重写AQS的方法,ReentrantLock实现了自己的获取锁和释放锁的逻辑。

  • 对于可重入锁(ReentrantLock,默认情况下为非公平锁),AQS中维护了一个持有锁的线程对象(owner)和一个重入次数(holdCount),通过这些信息来判断当前线程是否可以再次获取锁。
  • 对于公平锁,AQS中的等待队列(CLH队列)以及相关的线程状态(Node)被用来维护等待获取锁的线程顺序,确保先到先得的获取锁顺序。

通过继承AQSReentrantLock实现了对这些状态和逻辑的管理和控制,从而实现了可重入锁和公平锁的功能。

2.锁的获取与释放

  • 获取锁:可以使用ReentrantLock的lock()方法获取锁,如果锁已经被其他线程持有,则当前线程会被阻塞。
  • 释放锁:使用unlock()方法来释放锁,要确保在获取锁和释放锁的操作处于同一个代码块中。

3.可重入锁特性

  • ReentrantLock支持可重入性,即同一个线程可以多次获取锁而不会造成死锁。
  • 当一个线程已经获得锁时,它可以继续获取锁,而不会被自己持有的锁所阻塞。
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

下面示例,主线程进入one,再进入two,都是锁的重入

import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            System.out.println("执行main...");
            one();
        } finally {
            lock.unlock();
        }
    }

    private static void one() {
        lock.lock();
        try {
            System.out.println("执行one...");
            two();
        } finally {
            lock.unlock();
        }
    }

    private static void two() {
        System.out.println("执行two....");
    }

}

注意synchronized也是可重入的

4.公平锁特性

公平锁是一种锁获取的机制,它确保锁的获取按照线程的申请顺序进行。

  • 对于公平锁,当多个线程尝试获取锁时,锁的实现会维护一个等待队列,记录等待获取锁的线程。当锁被释放时,等待时间最长的线程将获得锁的访问权。这样就可以保证锁的获取按照线程的申请顺序进行。
  • 对于非公平锁,锁的获取不保证按照线程的申请顺序进行。当一个线程释放锁后,锁的实现可能会允许刚刚释放的锁立即被其他正在等待的线程获取,而不考虑等待时间的长短。

即公平与非公平是站在线程等待时间角度来讲,而不是多个线程同时启动时谁先获取锁的角度

优缺点

公平锁

  • 优点:公平性,能够避免线程饥饿,确保所有线程都有机会获取到锁。
  • 缺点:由于需要维护等待队列和按照顺序分配锁的开销,公平锁的性能通常比非公平锁低。

非公平锁

  • 优点:不需要维护等待队列和按照顺序分配锁,可以减少CPU唤醒线程的开销。
  • 缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

使用

ReentrantLock的默认构造方法创建的是非公平锁。如果不特别指定,ReentrantLock对象默认是非公平锁。如果需要使用公平锁,必须显式地通过构造方法参数设置为true

//创建lock为公平锁
ReentrantLock fairLock = new ReentrantLock(true);

ReentrantLock内部有一个抽象类内部类Sync继承了AQS,在创建公平锁时使用了这个**Sync**

image-20231107145758020

5.条件变量

ReentrantLock提供了条件变量(Condition)的支持,通过Condition对象可以实现线程间的等待和唤醒机制。条件变量可以更加灵活地控制线程的等待和唤醒,以实现更复杂的线程同步操作

条件变量的实现需要Condition接口与Lock接口配套使用,用于实现线程的等待和唤醒。它提供了和Object类中的wait()、notify()和notifyAll()方法类似的功能,但相比之下更加灵活和可控。

Condition接口定义了以下几个常用的方法:

  1. await():使当前线程进入等待状态,直到接收到信号或被中断。调用await()方法后,会释放当前线程持有的锁,并且进入等待状态。
  2. signal():唤醒一个等待在该Condition上的线程。调用signal()方法时,会从等待队列中选择一个线程,并使其进入可运行状态。
  3. signalAll():唤醒所有等待在该Condition上的线程。调用signalAll()方法时,会唤醒所有等待的线程,使它们进入可运行状态。

Condition接口实际上是通过Lock接口的newCondition()方法创建的,如下所示:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

注意事项:

  • 条件变量必须在持有相应ReentrantLock锁的情况下才能使用。
  • 唤醒等待线程时,需要获取相应的锁才能调用signal()或signalAll()方法。

代码示例

实现one等待two执行完成后再执行,并且one中要获取到two的结果result

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private Integer result = 0;
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();


    public static void main(String[] args) {

        Test test = new Test();

        Thread one = new Thread() {
            public void run() {
                lock.lock();
                try {
                    System.out.println("one开始执行");
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("one被唤醒继续执行,并得到future结果为:" + test.result);
                } finally {
                    lock.unlock();
                }

            }

        };

        one.start();

        Thread two = new Thread() {
            public void run() {
                lock.lock();
                try {
                    System.out.println("two开始执行");
                    test.result = 1;
                    System.out.println("two执行完毕,并唤醒one");
                    condition.signal();
                } finally {
                    lock.unlock();
                }
            }
        };

        two.start();
        
    }
}

结果

one开始执行
two开始执行
two执行完毕,并唤醒one
one被唤醒继续执行,并得到future结果为:1

运行过程

线程one获取锁。

线程one输出"one开始执行"。

线程one调用condition.await()方法,将当前线程置于等待状态,释放锁。

线程two获取锁。

线程two输出"two开始执行"。

线程two修改test.result的值为1。

线程two输出"two执行完毕,并唤醒one"。

线程two调用condition.signal()方法,唤醒一个等待在condition上的线程。

线程two释放锁。

线程one获取锁。

线程one被唤醒继续执行。

线程one输出"one被唤醒继续执行,并得到future结果为:1"。

线程one释放锁。

因为先执行one.start(),再执行two.start(),所以线程one会先通过lock.lock()获取锁,在调用condition.await()进行等待,因为Lock是独占锁,一次只能有一个线程持有锁,所以在one线程获取后,two会等待其释放。在one调用lock.unlock()释放独占锁后,线程two执行,并调用调用condition.signal()方法,然后线程one结束等待,继续执行。

6.各种方法的使用:

注意:在获取 持锁线程数、线程持锁次数时,一般都是估计值不是百分百准确,因为并发中,可能在获取到数值得同时又有新线程持有锁

1.可中断锁获取的方法

ReentrantLocklockInterruptibly()方法是一个可中断的获取锁的方法。它是Lock接口中的一个方法,用于获取锁并允许响应中断。

当线程A调用lockInterruptibly()方法时,会尝试获取锁。如果锁可用(没有其他线程持有锁),线程A将立即获取锁,并且可以继续执行后续代码。

如果锁不可用,即其他线程持有了锁并且未释放,线程A将被阻塞。被阻塞的线程A会进入一个等待状态,直到满足以下两个条件之一:

  1. 其他线程释放了锁,线程A成功获取到了锁,然后它可以继续执行后续代码。
  2. 线程A被中断,即其他线程调用了线程A的interrupt()方法进行中断操作。在这种情况下,lockInterruptibly()方法会立即抛出InterruptedException异常,并且线程A不能获取到锁。

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            try {
                // 如果没有竞争那么此方法就会获取 lock 对象锁
                // 如果有竞争就进入阻塞队列,可以被其它线程用 interruput 方法打断
                System.out.println("尝试获得锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t1线程没有获得锁,被打断...return");
                return;
            }

            try {
                System.out.println("t1线程获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        //主线程先获得了锁
        lock.lock();
        thread.start();
        Thread.sleep(1000);
        System.out.println("interrupt...打断t1");
        thread.interrupt();
    }

}

运行结果:

image-20231106142701544

过程解析:

  1. 主线程通过lock.lock()先获得锁。
  2. 然后启动thread线程,由于主线程Thread.sleep(1000)睡了1秒,会先调用thread的lock.lockInterruptibly(),尝试获得锁,但是锁在主线程手里,thread获取不到,进入阻塞状态。
  3. 然后主线程执行thread.interrupt(),thread线程被中断,并抛出异常结束方法,由于return,"t1线程获得了锁"并没有打印控制台

2.尝试获取锁的方法

ReentrantLocktryLock()方法是尝试获取锁的方法,它会立即返回一个布尔值来指示是否成功获取锁。

当线程调用tryLock()方法时,如果锁可用,则该线程立即获取锁,并返回true。如果锁不可用,即其他线程持有了锁并且未释放,那么该线程不会被阻塞,而是立即返回false,表示未能获取锁。

lock()方法和lockInterruptibly()方法不同的是,tryLock()方法不会引发线程的阻塞。如果锁不可用,线程会立即返回,并且可以根据返回的结果来决定后续的操作。

tryLock()方法有两个重载的版本:

  • tryLock():该版本的tryLock()方法会立即尝试获取锁,如果锁可用,则立即返回获取锁成功的结果(true),如果锁不可用,则立即返回获取锁失败的结果(false)。
  • tryLock(long timeout, TimeUnit unit):该版本的tryLock()方法会在指定的等待时间内尝试获取锁。它接受两个参数,timeout表示等待的时间数量,unit表示等待时间的单位。如果在等待时间内成功获取到锁,则返回获取锁成功的结果(true),如果等待时间结束仍未获取到锁,则返回获取锁失败的结果(false)。

注意:

  • 无论是无参的tryLock()方法还是带参数的tryLock(long timeout, TimeUnit unit)方法,都不会引发线程的阻塞。如果锁不可用,线程立即返回,不会等待。
  • 如果调用tryLock(long timeout, TimeUnit unit)方法时,等待时间设为0或负数,它将立即尝试获取锁一次,没有额外的等待时间。

使用tryLock()方法时需要注意以下几点:

  • 在获取锁之后,需要检查返回的布尔值来确定是否成功获取到了锁。
  • 如果获取到了锁,需要在适当的时候使用unlock()方法来释放锁,以避免死锁等问题。

无参tryLock()方法示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private Lock lock = new ReentrantLock();

    public void doSomething() {
        boolean acquired = lock.tryLock();//如果为true,则已经获取锁,这里就相当于lock.lock()了
        if (acquired) {
            try {
                // 执行需要保护的临界区代码
                System.out.println(Thread.currentThread().getName()+"执行业务代码");
            } finally {
                lock.unlock();
            }
        } else {
            // 未能获取锁,进行其他处理
            System.out.println(Thread.currentThread().getName()+"未能获取锁");
        }
    }

    public static void main(String[] args) {
        Test example = new Test();

        Thread t1 = new Thread(() -> {
            example.doSomething();
        });
        Thread t2 = new Thread(() -> {
            example.doSomething();
        });
		
		t1.start();
        t2.start();
        
    }

}

在上面的例子中,如果线程t1在执行doSomething()方法时,锁可用,那么它将成功获取锁并执行临界区代码。如果线程t2在执行doSomething()方法时,锁不可用,那么它将立即返回false,然后可以根据需要进行其他处理。

有参tryLock()方法示例

public class ReentrantLockExample {

    private Lock lock = new ReentrantLock();

    public void doSomething() {
        try {
            boolean acquired = lock.tryLock(2, TimeUnit.SECONDS);
            if (acquired) {
                try {
                    // 执行需要保护的临界区代码
                } finally {
                    lock.unlock();
                }
            } else {
                // 未能在2秒内获取锁,进行其他处理
            }
        } catch (InterruptedException e) {
            // 处理中断异常
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        Thread t1 = new Thread(() -> {
            example.doSomething();
        });
        Thread t2 = new Thread(() -> {
            example.doSomething();
        });

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

在上面的例子中,线程t1和t2在执行doSomething()方法时,会尝试在2秒内获取锁。如果在2秒内成功获取到锁,则执行临界区代码。如果2秒内未能获取到锁,则进行其他处理。

3.获取持有锁次数

ReentrantLockgetHoldCount()方法是用于获取当前线程持有锁的次数的方法。

当线程调用lock()方法成功获取锁后,它的持有锁的次数会增加1。如果线程再次成功获取锁,持有锁的次数会再次增加1。当线程调用unlock()方法释放锁时,持有锁的次数会减少1。如果持有锁的次数减少到0,表示当前线程已经完全释放了锁。

getHoldCount()方法返回当前线程持有锁的次数。如果当前线程没有持有锁,或者已经完全释放了锁,该方法返回0。

注意:

getHoldCount()方法是ReentrantLock的,不是它从Lock接口实现来的(本人测试在jdk1.8版本,其他没看)

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " holds the lock");
            System.out.println("Hold count: " + lock.getHoldCount());

            // 获取锁后再次获取锁
            lock.lock();
            try {
                System.out.println("Thread " + Thread.currentThread().getName() + " holds the lock again");
                System.out.println("Hold count: " + lock.getHoldCount());
            } finally {
                lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Test example = new Test();

        Thread t1 = new Thread(() -> {
            example.doSomething();
        });
        t1.start();
    }

}

结果:

Thread Thread-0 holds the lock
Hold count: 1
Thread Thread-0 holds the lock again
Hold count: 2
Thread Thread-0 holds the lock again and again
Hold count: 0

在上面的例子中,线程t1在执行doSomething()方法时,获取了锁并执行了临界区代码。在临界区代码中,线程t1又一次获取了锁。在调用getHoldCount()方法时,可以看到线程t1持有锁的次数为2。在释放锁后,持有锁的次数减少到0。

4.判断锁是否被当前线程持有

ReentrantLockisHeldByCurrentThread()方法是用于判断当前线程是否持有该锁的方法。

当线程调用lock()方法成功获取锁后,就会认为该线程持有了锁。而调用unlock()方法释放锁后,就会认为该线程不再持有锁。

isHeldByCurrentThread()方法会返回一个布尔值,用于指示当前线程是否持有该锁。如果当前线程持有锁,则返回true;如果当前线程不持有锁,则返回false。

下面是一个示例代码:

import java.util.concurrent.locks.ReentrantLock;

//判断当前线程是否持有lock的锁
public class Test {

    private ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " holds the lock");
            System.out.println("Is held by current thread? ----" + lock.isHeldByCurrentThread());
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Test example = new Test();

        Thread t1 = new Thread(() -> {
            example.doSomething();
        });
        t1.start();
    }

}

结果会输出:

Is held by current thread? ----true

如果注释掉lock.lock()lock.unlock(),就是false了,只注释lock.lock()不注释lock.unlock(),会报错java.lang.IllegalMonitorStateException

5.判断锁是否被任意线程持有

ReentrantLockisLocked()方法是用于判断锁是否被任何线程持有的方法。

isLocked()方法返回一个布尔值,用于指示锁当前是否被任何线程持有。如果锁被任何线程持有,则返回true;如果锁没有被任何线程持有,则返回false。

下面是一个示例代码:

import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private ReentrantLock lock = new ReentrantLock();

    public void doSomething() {

        System.out.println("锁被线程持有吗? "+lock.isLocked());
        
        lock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 持有锁");
            System.out.println("锁被线程持有吗? "+lock.isLocked());
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Test example = new Test();

        Thread t1 = new Thread(() -> {
            example.doSomething();
        });
        t1.start();
    }
}

结果:

锁被线程持有吗? false
线程 Thread-0 持有锁
锁被线程持有吗? true

6.判断锁是不是公平锁

ReentrantLockisFair()方法是用于判断锁是否是公平锁的方法。

公平锁是一种锁获取的机制,它确保锁的获取按照线程的申请顺序进行。当锁被释放时,等待时间最长的线程将获得锁的访问权。而非公平锁则没有这种保证,可能会导致等待时间较短的线程获取到锁。

isFair()方法返回一个布尔值,用于指示锁是否是公平锁。如果锁是公平锁,则返回true;如果锁是非公平锁,则返回false。

下面是一个示例代码:

import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private ReentrantLock fairLock = new ReentrantLock(true);
    private ReentrantLock unfairLock = new ReentrantLock(false);

    public void doSomething() {

        System.out.println("fairLock是公平锁吗? "+fairLock.isFair());

        System.out.println("unfairLock是公平锁吗? "+unfairLock.isFair());
    }

    public static void main(String[] args) {
        Test example = new Test();
        example.doSomething();
    }
}

结果:

fairLock是公平锁吗? true
unfairLock是公平锁吗? false

在上面的例子中,我们创建了一个公平锁(fairLock)和一个非公平锁(unfairLock)。在调用isFair()方法后,我们可以看到公平锁返回true,表示它是一个公平锁;而非公平锁返回false,表示它不是一个公平锁。

7.判断指定线程是否在队列中等待获取锁

ReentrantLock类提供了hasQueuedThread(Thread thread)方法,用于判断指定的线程,是否在等待队列中等待获取锁。

该方法接受一个Thread对象作为参数,返回一个boolean值。如果指定的线程在等待队列中等待获取锁,则返回true;否则返回false

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class Test{

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 1 获得锁");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Thread 1 释放锁");
            }
        });

        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 2 获得锁");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Thread 2 释放锁");
            }
        });

        thread1.start();

        try {
            Thread.sleep(1000);
            thread2.start();
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean isThread1Queued = lock.hasQueuedThread(thread1);
        boolean isThread2Queued = lock.hasQueuedThread(thread2);

        System.out.println("Thread 1 是否在等待队列中? " + isThread1Queued);
        System.out.println("Thread 2 是否在等待队列中? " + isThread2Queued);
    }

}

结果:

Thread 1 获得锁
Thread 1 是否在等待队列中? false
Thread 2 是否在等待队列中? true
Thread 1 释放锁
Thread 2 获得锁
Thread 2 释放锁

8.判断是否有线程在队列中等待获取锁

ReentrantLockhasQueuedThreads()方法,是用于判断是否有线程正在等待获取锁的方法。

与上面不同的是它无法指定线程判断

方法签名: boolean hasQueuedThreads()

返回值: 如果有线程正在等待获取锁,则返回true,否则返回false。

方法说明: 该方法将返回一个布尔值,指示是否有线程正在等待获取锁。如果有线程在等待获取锁,则返回true,否则返回false。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法不会修改锁的状态,仅用于查询是否有线程在等待获取锁。

示例代码:

import java.util.concurrent.locks.ReentrantLock;

public class Test{

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

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

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("hasQueuedThreads: " + lock.hasQueuedThreads());
    }

}

在上面的示例代码中,我们创建了两个线程t1和t2,它们都会获取锁并睡眠2秒钟,然后释放锁。在主线程中,我们调用了lock的hasQueuedThreads方法来判断是否有线程正在等待获取锁。由于t1和t2线程都会等待获取锁,所以hasQueuedThreads方法返回true。

9.返回正在等待获取锁的线程数

ReentrantLockgetQueueLength方法是用于获取等待获取锁的线程数的方法

方法签名: int getQueueLength()

返回值: 返回正在等待获取锁的线程数。

方法说明: 该方法将返回一个整数,表示当前正在等待获取锁的线程数。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法不会修改锁的状态,仅用于查询等待获取锁的线程数。

示例代码:

import java.util.concurrent.locks.ReentrantLock;

public class Test{

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

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

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println("Queue Length: " + lock.getQueueLength());
    }

}

结果:

Queue Length: 1

10.获取等待获取锁的线程列表

ReentrantLock的getQueuedThreads方法是用于获取正在等待获取锁的线程列表的方法。

方法签名: Collection getQueuedThreads()

返回值: 返回一个包含正在等待获取锁的线程的集合。

方法说明: 该方法将返回一个包含当前正在等待获取锁的线程的集合。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法不会修改锁的状态,仅用于查询等待获取锁的线程列表。

此方法为protected受保护暂不做示例代码

11.判断是否有线程在等待某个特定条件

ReentrantLockhasWaiters()方法是用于判断是否有线程在等待某个特定条件的方法

方法签名: boolean hasWaiters(Condition condition)

参数:

  • condition:一个Condition对象,用于表示某个特定条件。

返回值: 如果有线程在等待指定条件的话,则返回true,否则返回false。

方法说明: 该方法将返回一个布尔值,指示是否有线程在等待指定条件。如果有线程在等待指定条件,则返回true,否则返回false。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法不会修改锁的状态,仅用于查询是否有线程在等待指定条件。
  • 该方法要在获取锁的状态下才能调用

示例代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

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

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		
        //要在有锁的情况下调用
        lock.lock();
        try {
            System.out.println("hasWaiters: " + lock.hasWaiters(condition));
        } finally {
            lock.unlock();
        }
    }
}

结果:

t1与t2都在通过 condition.await()进行等待

hasWaiters: true

12.获取某个条件等待锁的线程数

ReentrantLockgetWaitQueueLength方法是用于获取等待与此锁关联的给定条件的线程数的方法。

方法签名: int getWaitQueueLength(Condition condition)

参数:

  • condition:一个Condition对象,表示一个特定条件。

返回值: 返回正在等待与此锁关联的指定条件的线程数。

方法说明: 该方法将返回一个整数,表示正在等待与此锁关联的指定条件的线程数。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法需要在获取锁之后使用,因为它依赖于锁的状态。
  • 该方法要在获取锁的状态下才能调用

示例代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

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

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		
        //该方法要在获取锁的状态下才能调用
        lock.lock();
        try {
            System.out.println("WaitQueueLength: " + lock.getWaitQueueLength(condition));
        } finally {
            lock.unlock();
        }
    }
}

结果:

t1与t2都在通过 condition.await()进行等待

WaitQueueLength: 2

13.获取等待某个条件的线程列表

ReentrantLockgetWaitingThreads(Condition condition)方法是用于获取等待给定条件的线程列表。

方法签名: Collection getWaitingThreads

参数:

  • condition:一个Condition对象,表示一个特定条件。

返回值: 返回正在等待与此锁关联的指定条件的线程集合。

方法说明: 该方法将返回一个集合,表示正在等待与此锁关联的指定条件的线程对象。

注意事项:

  • 该方法返回的结果是一个估计值,因为在调用该方法的瞬间,可能有新的线程进入等待队列。
  • 此方法需要在获取锁之后使用,因为它依赖于锁的状态。
  • 该方法要在获取锁的状态下才能调用

示例代码:

由于getWaitingThreads方法的访问修饰符protected为受保护,需要继承ReentrantLock来使用:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Test extends ReentrantLock {

    private static Test lock = new Test();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

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

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        lock.lock();
        try {
            System.out.println("WaitQueueLength: " + lock.getWaitingThreads(condition));
        } finally {
            lock.unlock();
        }
    }

}

结果:

获取到t1与t2两个等待线程

WaitQueueLength: [Thread[Thread-0,5,main], Thread[Thread-1,5,main]]

读写锁ReentrantReadWriteLock

ReentrantReadWriteLock 是 Java.util.concurrent(JUC)包中的一个类,用于提供读写分离的锁机制。它是对传统的互斥锁(ReentrantLock)的一种扩展,提供了更细粒度的控制。

ReentrantReadWriteLock 的特点如下:

  1. 读锁(共享锁):多个线程可以同时获得读锁,只要没有线程持有写锁。读锁在没有写锁的情况下是共享的,因此可以提高并发性能。
  2. 写锁(排他锁):只有一个线程可以获得写锁。当一个线程持有写锁时,其他线程无法获得读锁或写锁。
  3. 重入性:与 ReentrantLock 一样,ReentrantReadWriteLock 也是可重入的,同一个线程可以多次获取读锁或写锁。
  4. 公平与非公平:与 ReentrantLock 一样,ReentrantReadWriteLock 可以通过构造方法指定公平性
  5. 条件变量:ReentrantReadWriteLock没有提供像Object类中的wait()和notify()方法那样的等待/通知机制。为了实现类似的等待/通知功能,可以使用Condition接口。Condition对象必须与ReentrantReadWriteLock的写锁关联,因为写锁是互斥的,只有一个线程可以获取。当条件满足时,调用Condition的signal()或signalAll()方法会唤醒等待的线程。

ReentrantReadWriteLock 提供了以下主要的方法:

  • readLock():返回读锁。
  • writeLock():返回写锁。

读锁

也叫共享锁,读锁在没有写锁的情况下是共享的,可以并发执行,因此可以提高并发性能。

(注意ReentrantReadWriteLock的读锁是悲观读,即有写锁被持有,进行写操作时,读锁会等待写完在进行读操作,乐观锁的实现参考后面的StampedLock)

代码示例:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock()

通过readLock()获取读锁,实际为ReentrantReadWriteLock的内部类ReadLock,然后用读锁的lock()上锁

  • 后续通过hasQueuedThread(thread)方法查看指定线程是否在队列中等待获取锁
  • 如果是false,则证明没有线程等待锁,共享锁生效
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static String resource = "initial data";

    public static void main(String[] args) {
        
        // 两个线程可以同时读取资源
        Thread reader1 = new Thread(() -> {
            //readLock:获取读锁,实际获取的是ReentrantReadWriteLock的内部类ReadLock
            //lock:用获取的读锁来上锁
            lock.readLock().lock();
            try {

                System.out.println("Reader 1: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 1 结束");
            }
        });

        Thread reader2 = new Thread(() -> {
            lock.readLock().lock();
            try {

                System.out.println("Reader 2: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 2 结束");
            }
        });

        reader1.start();
        reader2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 通过hasQueuedThread(thread)方法查看指定线程是否在队列中等待获取锁
         *  如果是false,则证明没有线程等待锁,共享锁生效
         * */
        System.out.println("reader 1 是否在等待队列中? " + lock.hasQueuedThread(reader1));
        System.out.println("reader 2 是否在等待队列中? " + lock.hasQueuedThread(reader2));
    }
}

结果:

Reader 1: initial data
Reader 2: initial data
reader 1 是否在等待队列中? false
reader 2 是否在等待队列中? false
Reader 1 结束
Reader 2 结束

写锁

也叫排他锁,只有一个线程可以获得写锁。当一个线程持有写锁时,其他线程无法获得读锁或写锁。

注意:当读写锁同时存在时,读锁和写锁之间也会等待,ReentrantReadWriteLock的读为悲观读

概括为ReentrantReadWriteLock中,读读共享,读写 与 写写都会等待

代码示例:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();

通过writeLock()获取写锁,实际为ReentrantReadWriteLock的内部类WriteLock,然后用写锁的lock()上锁

  • 后续通过hasQueuedThread(thread)方法查看指定线程是否在队列中等待获取锁
  • 如果有一个为true,则证明有线程等待锁,排他锁生效
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test{

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static String resource = "initial data";

    public static void main(String[] args) {

        //读
        Thread read1 = new Thread(() -> {
            lock.readLock().lock();
            try {

                System.out.println("read1开始于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                resource = "new data1";
                System.out.println("read1: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("read1结束于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        // 写线程需要获取独占写锁
        Thread writer1 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer1开始于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                resource = "new data1";
                System.out.println("Writer1: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
                System.out.println("Writer1结束于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        // 写线程需要获取独占写锁
        Thread writer2 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer2开始于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                resource = "new data2";
                System.out.println("Writer2: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
                System.out.println("Writer2结束于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        read1.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        writer1.start();
        writer2.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 通过hasQueuedThread(thread)方法查看指定线程是否在队列中等待获取锁
         *  如果有一个为true,则证明有线程等待锁,排他锁生效
         * */
        System.out.println("writer 1 是否在等待队列中? " + lock.hasQueuedThread(writer1));
        System.out.println("writer 2 是否在等待队列中? " + lock.hasQueuedThread(writer2));
        System.out.println("read 1 是否在等待队列中? " + lock.hasQueuedThread(read1));
    }

}

结果:

read1开始于2023-11-09 08:15:09
read1: new data1

writer 1 是否在等待队列中? true
writer 2 是否在等待队列中? true
read 1 是否在等待队列中? false

read1结束于2023-11-09 08:15:12

Writer1开始于2023-11-09 08:15:12
Writer1: new data1

Writer2开始于2023-11-09 08:15:15
Writer1结束于2023-11-09 08:15:15

Writer2: new data2
Writer2结束于2023-11-09 08:15:18

分析:read1读锁未释放时,writer1 与 writer2会等待,而且两个写锁也会等待对方释放再执行。如果写锁在前,读锁一样会等待

公平锁特性

ReentrantReadWriteLock也可以实现公平与非公平,默认为非公平

ReentrantReadWriteLock中,默认是使用非公平锁的,可以通过构造函数ReentrantReadWriteLock(boolean fair)来选择是否使用公平锁。通过使用公平锁,我们可以确保线程的公平性,避免优先级倒置和饥饿现象,并更好地控制线程的访问和并发操作。

//公平的读写锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

ReentrantReadWriteLock内部有一个抽象类内部类Sync继承了AQS,在创建公平锁时使用了这个Sync,这点与ReentrantLock的公平锁实现一致。

image-20231107145444025

而且内部同样是维护了等待队列:

  1. 当一个线程请求锁但锁已被其他线程持有时,该线程会被放入等待队列中(末尾)
  2. 等待队列中的线程按照请求的先后顺序进行排列,即先进入等待队列的线程会先获得锁
  3. 当锁的持有者释放锁的时候,ReentrantReadWriteLock会从等待队列中选择一个线程分配锁
  4. 而公平锁会选择等待队列中最先入队的线程,将锁分配给它

原理

ReentrantReadWriteLock中,有三个内部类

  • Sync
  • ReadLock
  • WriteLock

读写锁的核心实现,来源与AQSvolatile整数变量state

state的高16位用于存储共享锁计数器,低16位用于存储排他锁计数器(因为java的int有32位)。

  • 获取读锁,则会将state高16位(共享锁计数器)增加1。
  • 获取写锁,则会将state低16位(排他锁计数器)加1。

在调用unlock()方法释放读锁或写锁时,会将state高16位(共享锁计数器)或低16位(排他锁计数器)递减。如果递减后的计数器为0,表示当前线程已释放所有的读锁或写锁,此时会将state归零。

**需要注意的是:**在释放锁之前,**同一个线程可以重入**它已经持有的读锁或写锁。这意味着同一个线程可以多次获取相同类型的锁,每次获取锁时计数器会递增,而释放锁时计数器会递减,直到计数器为0时才会彻底释放锁。

1.Sync

Sync,继承与AbstractQueuedSynchronizer,也即是AQS

ReentrantReadWriteLock内部的读锁与写锁,都通过这个Sync来实现

image-20231109083306434

2.读锁

ReadLock,使用syncacquireShared方法

acquireShared方法由AQS继承而来,主要作用是尝试获取共享锁,如果获取成功,则方法会立即返回;否则,线程会被加入到等待队列中,并在获取到共享锁时被唤醒。

image-20231109083747933

具体来说,acquireShared()方法的内部实现如下:

  1. 首先,该方法会调用tryAcquireShared()方法尝试获取共享锁。tryAcquireShared()方法是一个抽象方法,需要由具体的同步器类(如ReentrantReadWriteLock)实现。如果tryAcquireShared()返回一个大于等于0的值,表示获取共享锁成功;如果返回一个小于0的值,表示获取共享锁失败。
  2. 如果tryAcquireShared()返回大于等于0的值,表示获取共享锁成功,acquireShared()方法会直接返回。
  3. 如果tryAcquireShared()返回小于0的值,表示获取共享锁失败,线程会被加入到等待队列中,并进入等待状态。
  4. unlock释放共享锁时,因为ReentrantReadWriteLock是悲观读,会调用releaseShared()方法来唤醒等待队列中的一个或多个线程。被唤醒的线程会再次尝试获取共享锁,直到成功获取为止。

acquireShared内部有两个方法

image-20231110091015958

2.1尝试获取共享锁

tryAcquireShared尝试获取共享锁

首先,会调用tryAcquireShared()方法尝试获取共享锁。tryAcquireShared()方法是一个抽象方法,需要由具体的同步器类(这里是ReentrantReadWriteLock)实现。如果tryAcquireShared()返回一个大于等于0的值,表示获取共享锁成功;如果返回一个小于0的值,表示获取共享锁失败

下面是尝试获取共享锁的步骤:

1.先验证是否存在写锁

这里通过调用exclusiveCount(c)方法来检查是否存在排他锁。如果exclusiveCount(c)的返回值不为0(表示存在排他锁),并且通过getExclusiveOwnerThread() != current获取到的排他锁的拥有线程不是当前线程,那么就说明存在写锁并且不属于当前线程,此时会返回-1,表示获取共享锁失败。

image-20231110092229299

exclusiveCount方法通过位运算来检查是否存在排他锁。以下是代码解释:

EXCLUSIVE_MASK`是一个常量,用于提取`state`中排他锁计数器的位。在`ReentrantReadWriteLock`中,`state`的高16位用于存储共享锁计数器,低16位用于存储排他锁计数器。

`exclusiveCount()`方法中的位运算使用了按位与操作符`&`,它可以将`state`中的低16位全部置为0,只保留高16位的值。这样做的效果就是提取出了`state`中排他锁计数器的值。

因此,`exclusiveCount()`方法返回的值就是`state`中的排他锁计数器的值。如果返回的结果不为0,就表示存在排他锁;如果返回的结果为0,表示不存在排他锁。通过这个方法,可以检查锁的状态中是否存在排他锁。

image-20231110092723511

关于getExclusiveOwnerThread() != current,为什么还要检查获取的排他锁的拥有线程是不是当前线程,原因是对于ReentrantReadWriteLock,它允许一个线程同时拥有读锁和写锁,这种情况下不会产生互斥。

这种设计决策的原因是:读锁和写锁之间的互斥是在不同的线程之间保持的,而对于同一个线程来说,读锁和写锁之间是允许重入的。这样可以提高并发性能,允许多个线程同时读取共享资源,同时在需要修改共享资源时,写锁会阻止其他线程的读锁获取。

2.检查共享锁计数器值并更新

下图红框验证了state计数器:

image-20231110091435271

首先,它检查当前共享锁的数量r是否小于最大允许数量MAX_COUNT,以确保没有超过共享锁的上限。然后,它调用compareAndSetState(c, c + SHARED_UNIT)方法来尝试原子地将state的值从c更新为c + SHARED_UNIT。如果更新成功,表示获取共享锁成功,会执行相关的操作,如更新计数、设置第一个读取线程等。并结束方法return一个1,获取所成功。如果更新不成功,则会进入fullTryAcquireShared(current)方法进行完整的获取共享锁操作。

2.2从等待队列获取锁

利用无限for循环,不断从队列第一个节点拿去线程去尝试获取锁,取到就结束循环返回,取不到就继续循环获取

image-20231110082225843

需要注意的是,AQS中的等待队列使用的是CLH(Craig, Landin, and Hagersten)队列算法,它是一种基于链表的队列,能够高效地实现线程的等待和唤醒。在CLH队列中,新的等待线程是从队尾开始放置的。也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。

3.写锁

WriteLock,使用syncacquire方法

acquire方法由AQS继承而来,主要作用是尝试获取独占锁,如果获取成功,则方法会立即返回;否则,线程会被加入到等待队列中,并在获取到独占锁时被唤醒。

image-20231109083900076

具体来说,acquire()方法的实现如下:

  1. 首先,该方法会调用tryAcquire()方法尝试获取独占锁。tryAcquire()方法是一个抽象方法,需要由具体的同步器类(如ReentrantLock)实现。如果tryAcquire()返回true,表示获取独占锁成功;如果返回false,表示获取独占锁失败。
  2. 如果tryAcquire()返回true,表示获取独占锁成功,acquire()方法会直接返回。
  3. 如果tryAcquire()返回false,表示获取独占锁失败,线程会被加入到等待队列中,并进入等待状态。
  4. 当其他线程释放独占锁时,会调用release()方法来唤醒等待队列中的一个或多个线程。被唤醒的线程会再次尝试获取独占锁,直到成功获取为止。

image-20231110135427726

3.1 尝试获取写锁

tryAcquire尝试获取排他锁

tryAcquire尝试获取排他锁,获取不到就放入等待队列,再从队列循环获取

protected final boolean tryAcquire(int acquires) {
    		//获取当前线程
            Thread current = Thread.currentThread();
    		//获取state锁的32位码
            int c = getState();
    		//验证state的值,w是写锁的值
            int w = exclusiveCount(c);
    		
    		//C!=0则代表至少有一个锁被持有
            if (c != 0) {
                //这个条件判断的目的是确保当前线程只能获取自己独占锁的写锁,而非直接获取其他线程持有的写锁。
                //目的是判断是否可以进行可重入获取锁操作,注意c != 0这个前提
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //上面没返回false,那么就是有写锁被持有。
                //如果独占锁计数值加上acquires后超过了最大允许的锁数量,则抛出错误,表示锁数量超过了最大限制。
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                
                //上面两个都没执行
                //则进行可重入获取锁操作,即将当前状态值增加acquires,表示当前线程获取了写锁。最后返回true,表示获取锁成功。
                setState(c + acquires);
                return true;
            }
    
    		//writerShouldBlock表示持有读锁的线程数量大于0,则不能返回,因为悲观读与写锁互斥
    		//compareAndSetState方法会尝试将当前状态增加acquires,成功则获取锁。否则表示有其他线程同时尝试获取写锁,返回false,表示无法获取锁
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
    		
    		//将当前线程设置为独占锁的持有者。这样,在释放独占锁时,可以检查当前线程是否是持有独占锁的线程,以确保只有持有锁的线程才能释放锁。
            setExclusiveOwnerThread(current);
            return true;
        }
3.2从等待队列获取写锁

同样是利用无限循环for(;;),不断从队列取线程尝试获取锁

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued(final Node node, int arg)方法,用于在队列中获取锁。以下是代码逻辑的解释:

  1. 首先,定义一个布尔变量failed,初始值为true,用于标记获取锁是否失败。
  2. 然后,使用try-finally块,保证无论获取锁是否成功,都能执行释放与处理相关的操作。
  3. try块中,使用无限循环for(;;),不断尝试获取锁。
  4. 在循环中,首先获取当前节点的前驱节点p
  5. 如果前驱节点p是头节点(head)并且成功地通过tryAcquire(arg)方法尝试获取锁,则将当前节点设置为头节点,断开p的next指针(帮助GC),并将failed标记设置为false,返回interrupted标记,表示是否中断过线程。
  6. 如果上一步未能成功获取锁,则通过调用shouldParkAfterFailedAcquire(p, node)方法判断是否应该在获取锁失败后进行阻塞,并通过parkAndCheckInterrupt()方法进行线程阻塞。
  7. 如果parkAndCheckInterrupt()方法返回true,表示当前线程被中断过,将interrupted标记设置为true
  8. 重复以上步骤,直到成功获取锁为止。
  9. 最后,在finally块中,检查是否获取锁失败,如果是,则调用cancelAcquire(node)方法取消获取锁操作。

需要注意的是,AQS中的等待队列使用的是CLH(Craig, Landin, and Hagersten)队列算法,它是一种基于链表的队列,能够高效地实现线程的等待和唤醒。在CLH队列中,新的等待线程是从队尾开始放置的。也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。

读写锁各种方法的使用

注意:在获取 持锁线程数、线程持锁次数时,一般都是估计值不是百分百准确,因为并发中,可能在获取到数值得同时又有新线程持有锁

1.判断当前锁是否被写线程独占持有

ReentrantReadWriteLockisWriteLocked()方法是用于判断当前锁是否被写线程独占持有的方法。

具体解释如下:

  1. 方法定义:
    • boolean isWriteLocked()
  2. 方法说明:
    • isWriteLocked()方法用于判断当前ReentrantReadWriteLock是否被写线程独占持有。
    • 如果当前存在一个线程持有写锁,则返回true,否则返回false。
  3. 使用场景:
    • 通过isWriteLocked()方法,可以在某些情况下判断是否有其他线程正在写入数据,来进行相应的处理逻辑。
    • 可以在读线程中使用isWriteLocked()方法来判断是否需要等待写锁释放,避免读写冲突。

需要注意的是,isWriteLocked()方法只能判断当前时刻是否存在一个线程持有写锁,并不能判断是否有其他线程在等待写锁。如果需要判断是否有其他线程在等待写锁,可以使用ReentrantReadWriteLock的getQueuedWriterThreads()方法。

示例代码如下:

public class ReentrantReadWriteLockExample {

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static String resource = "initial data";

    public static void main(String[] args) {
        
        Thread writer = new Thread(() -> {
            lock.writeLock().lock();
            try {
                resource = "new data";
                System.out.println("Writer: " + resource);
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });

        writer.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("是否有线程持有写锁? " + lock.isWriteLocked());

    }
}

结果为:

Writer: new data
是否有线程持有写锁? true

通过调用ReentrantReadWriteLockisWriteLocked()方法,可以方便地判断当前是否存在写线程独占持有锁,从而进行相应的处理。

2.获取当前读锁的持有线程数

ReentrantReadWriteLockgetReadLockCount()方法用于获取当前读锁的持有数。

具体解释如下:

  1. 方法定义:
    • int getReadLockCount()
  2. 方法说明:
    • getReadLockCount()方法用于获取当前ReentrantReadWriteLock中读锁的持有数。
    • 返回值是一个int类型的数值,表示当前读锁的持有数。
  3. 使用场景:
    • 可以通过getReadLockCount()方法获取当前读锁的持有数,用于统计读锁的使用情况,监控系统的读操作并进行相应的处理逻辑。
    • 可以在写线程中使用getReadLockCount()方法来判断是否需要等待读锁释放,避免读写冲突。

需要注意的是,getReadLockCount()方法只能获取当前时刻的读锁持有数,并不能获取到之前已经释放的读锁的总数。如果需要获取总的读锁持有数,可以结合其他计数变量进行统计。

示例代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static String resource = "initial data";

    public static void main(String[] args) {

        // 两个线程可以同时读取资源
        Thread reader1 = new Thread(() -> {
            //readLock:获取读锁,实际获取的是ReentrantReadWriteLock的内部类ReadLock
            //lock:用获取的读锁来上锁
            lock.readLock().lock();
            try {

                System.out.println("Reader 1: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 1 结束");
            }
        });

        Thread reader2 = new Thread(() -> {
            lock.readLock().lock();
            try {

                System.out.println("Reader 2: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 2 结束");
            }
        });

        reader1.start();
        reader2.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 获取当前读锁的持有数
        int readLockCount = lock.getReadLockCount();
        System.out.println("当前读锁的持有数:" + readLockCount);

    }
}

结果输出:当前读锁的持有数:2

3.判断当前线程是否独占持有写锁

ReentrantReadWriteLockisWriteLockedByCurrentThread()方法是用于判断当前线程是否独占持有写锁的方法。

具体解释如下:

  1. 方法定义:
    • boolean isWriteLockedByCurrentThread()
  2. 方法说明:
    • isWriteLockedByCurrentThread()方法用于判断当前线程是否独占持有写锁。
    • 如果当前线程持有写锁,则返回true,否则返回false。
  3. 使用场景:
    • 可以在代码中使用isWriteLockedByCurrentThread()方法来判断当前线程是否持有写锁,根据结果执行相应的逻辑。
    • 在写入操作之前,可以使用isWriteLockedByCurrentThread()方法来判断是否已经持有写锁,避免重入写锁导致的死锁。

需要注意的是,isWriteLockedByCurrentThread()方法只能判断当前线程是否独占持有写锁,并不能判断是否有其他线程在等待写锁。如果需要判断是否有其他线程在等待写锁,可以使用ReentrantReadWriteLockgetQueuedWriterThreads()方法。

示例代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test{

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {

        // 写线程需要获取独占写锁
        Thread writer1 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer1是否独占持有写锁? "+lock.isWriteLockedByCurrentThread());
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });

        // 写线程需要获取独占写锁
        Thread writer2 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer2是否独占持有写锁? "+lock.isWriteLockedByCurrentThread());
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });

        writer1.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程是否独占持有写锁? "+lock.isWriteLockedByCurrentThread());

        writer2.start();
        
    }
}

结果:

Writer1是否独占持有写锁? true
主线程是否独占持有写锁? false
Writer2是否独占持有写锁? true

4.获取当前线程持有写锁的次数

ReentrantReadWriteLockgetWriteHoldCount()方法是用于获取当前线程持有写锁的次数的方法

具体解释如下:

  1. 方法定义:
    • int getWriteHoldCount()
  2. 方法说明:
    • getWriteHoldCount()方法用于获取当前线程持有写锁的次数。
    • 返回值是一个int类型的数值,表示当前线程持有写锁的次数。
  3. 使用场景:
    • 可以通过getWriteHoldCount()方法获取当前线程持有写锁的次数,用于统计写锁的重入次数,监控系统的写操作并进行相应的处理逻辑。
    • 可以在写线程中使用getWriteHoldCount()方法来判断是否需要嵌套执行写操作,从而避免死锁。

需要注意的是,getWriteHoldCount()方法只能获取当前线程独占持有写锁的次数,并不能获取到之前已经释放的写锁的总次数。如果需要获取总的写锁持有次数,可以结合其他计数变量进行统计。

示例代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test{

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        
        Thread writer1 = new Thread(() -> {
            lock.writeLock().lock();
            System.out.println("writer1线程独占持有写锁的次数:" + lock.getWriteHoldCount());
            lock.writeLock().unlock();

        });
        
        Thread writer2 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                lock.writeLock().lock();
                System.out.println("writer2线程独占持有写锁的次数:" + lock.getWriteHoldCount());
                lock.writeLock().unlock();

            }  finally {
                lock.writeLock().unlock();
            }
        });
        
        writer1.start();
        writer2.start();

    }
}

结果:

writer1线程独占持有写锁的次数:1
writer2线程独占持有写锁的次数:2

5.获取当前线程持有读锁的次数

ReentrantReadWriteLockgetReadHoldCount()方法是用于获取当前线程持有读锁的次数的方法。

具体解释如下:

  1. 方法定义:
    • int getReadHoldCount()
  2. 方法说明:
    • getReadHoldCount()方法用于获取当前线程持有读锁的次数。
    • 返回值是一个int类型的数值,表示当前线程持有读锁的次数。
  3. 使用场景:
    • 可以通过getReadHoldCount()方法获取当前线程持有读锁的次数,用于统计读锁的重入次数,监控系统的读操作并进行相应的处理逻辑。
    • 在嵌套的读锁场景中,可以使用getReadHoldCount()方法来判断是否需要嵌套执行读操作,从而避免死锁。

需要注意的是,getReadHoldCount()方法只能获取当前线程持有读锁的次数,并不能获取到之前已经释放的读锁的总次数。如果需要获取总的读锁持有次数,可以结合其他计数变量进行统计。

示例代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test{

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {

        Thread reader1 = new Thread(() -> {
            lock.readLock().lock();
            System.out.println("reader1线程独有读锁的次数:" + lock.getReadHoldCount());
            lock.readLock().unlock();

        });

        Thread reader2 = new Thread(() -> {
            lock.readLock().lock();
            try {

                lock.readLock().lock();
                System.out.println("reader2线程持有读锁的次数:" + lock.getReadHoldCount());
                lock.readLock().unlock();

            }  finally {
                lock.readLock().unlock();
            }
        });

        reader1.start();
        reader2.start();

    }
}

结果:

reader1线程独有读锁的次数:1
reader2线程持有读锁的次数:2

6.获取正在等待写锁的线程列表

ReentrantReadWriteLockgetQueuedWriterThreads()方法用于获取正在等待写锁的线程列表。

具体解释如下:

  1. 方法定义:
    • Collection getQueuedWriterThreads()
  2. 方法说明:
    • getQueuedWriterThreads()方法用于获取正在等待写锁的线程列表。
    • 返回值是一个Collection类型,包含了正在等待写锁的线程。
  3. 使用场景:
    • 可以通过getQueuedWriterThreads()方法获取当前正在等待写锁的线程列表,用于监控系统的写锁竞争情况,进行相应的处理逻辑。
    • 可以在写线程中使用getQueuedWriterThreads()方法来判断是否有其他线程在等待写锁,从而根据实际需求调整写操作的优先级或执行策略。

需要注意的是,getQueuedWriterThreads()方法只能获取当前正在等待写锁的线程列表,并不能获取到之前已经获取到写锁的线程列表。如果需要获取已经获取到写锁的线程列表,可以结合其他方法进行判断。

示例代码如下:

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//getQueuedWriterThreads是受保护方法,需要继承ReentrantReadWriteLock使用
public class Test extends ReentrantReadWriteLock
{

    private static Test lock = new Test();

    public static void main(String[] args) {

        // 写线程需要获取独占写锁
        Thread writer1 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer1开始于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
                System.out.println("Writer1结束于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        // 写线程需要获取独占写锁
        Thread writer2 = new Thread(() -> {
            lock.writeLock().lock();
            try {

                System.out.println("Writer2开始于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
                System.out.println("Writer2结束于"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        writer1.start();
        writer2.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 获取正在等待写锁的线程列表
        Collection<Thread> queuedWriterThreads = lock.getQueuedWriterThreads();
        System.out.println("正在等待写锁的线程列表:" + queuedWriterThreads);

    }

}

结果:

无论1和2谁先执行,总会有另一个在等待

Writer1开始于2023-11-08 08:12:55
正在等待写锁的线程列表:[Thread[Thread-1,5,main]]
Writer2开始于2023-11-08 08:12:58
Writer1结束于2023-11-08 08:12:58
Writer2结束于2023-11-08 08:13:01

7.获取正在等待读锁的线程列表

ReentrantReadWriteLock类的getQueuedReaderThreads方法返回一个包含正在等待获取读锁的线程的集合。

具体解释如下:

  1. 方法定义:
    • Collection getQueuedReaderThreads()
  2. 方法说明:
    • getQueuedReaderThreads()方法用于获取正在等待读锁的线程列表。
    • 返回值是一个Collection类型,包含了正在等待读锁的线程。

需要注意的是,getQueuedReaderThreads()方法只能获取当前正在等待读锁的线程列表,并不能获取到之前已经获取到读锁的线程列表。如果需要获取已经获取到写锁的线程列表,可以结合其他方法进行判断。

代码示例:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample extends ReentrantReadWriteLock{

    private static ReentrantReadWriteLockExample lock = new ReentrantReadWriteLockExample();
    private static String resource = "initial data";

    public static void main(String[] args) {

        // 两个线程可以同时读取资源
        Thread reader1 = new Thread(() -> {
            //readLock:获取读锁,实际获取的是ReentrantReadWriteLock的内部类ReadLock
            //lock:用获取的读锁来上锁
            lock.readLock().lock();
            try {

                System.out.println("Reader 1: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 1 结束");
            }
        });

        Thread reader2 = new Thread(() -> {
            lock.readLock().lock();
            try {

                System.out.println("Reader 2: " + resource);
                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println("Reader 2 结束");
            }
        });

        reader1.start();
        reader2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("等待读锁的线程: " + lock.getQueuedReaderThreads());

    }
}

结果:

读锁不等待,所以线程列表为空

Reader 1: initial data
Reader 2: initial data
等待读锁的线程: []
Reader 1 结束
Reader 2 结束

8.其他

有一些方法与上面ReentrantLock的方法大都一样,但使用时需要指明读锁或写锁。

中断锁获取

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

lock.writeLock().lockInterruptibly();

尝试获取锁的方法

有参的

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

lock.writeLock().tryLock();//如果为true,则已经获取锁,这里就相当于lock.lock()了

无参的

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

lock.writeLock().tryLock(2, TimeUnit.SECONDS);//如果为true,则已经获取锁,这里就相当于lock.lock()了

获取持有锁次数

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().getHoldCount();

判断锁是否被当前线程持有

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

lock.writeLock().isHeldByCurrentThread()

判断锁是不是公平锁

public class Test{

    private ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
    private ReentrantReadWriteLock unfairLock = new ReentrantReadWriteLock(false);

    public void doSomething() {

        System.out.println("fairLock是公平锁吗? "+fairLock.isFair());

        System.out.println("unfairLock是公平锁吗? "+unfairLock.isFair());
    }

    public static void main(String[] args) {
        Test example = new Test();
        example.doSomething();
    }
}

判断是否有线程在队列中等待获取锁

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();
lock.writeLock().unlock();
//判断是否有线程在队列中等待获取锁,有就返回true
lock.hasQueuedThreads();

其他方法

上面ReentrantLockzh有的方法ReentrantReadWriteLock也都有,不一一列举

StampedLock邮戳锁

1.简介

StampedLock 是 Java.util.concurrent(JUC)包中的一个类,用于提供乐观读锁悲观读锁写锁的机制。它是在 JDK 8 中引入的,相比于 ReentrantReadWriteLock,StampedLock 提供了更高的并发性能。

StampedLock 的特点如下:

  1. 乐观读锁:可以同时允许多个线程持有读锁,读取共享数据,即使有线程持有写锁,也不会阻塞读锁的读取。
  2. 悲观读锁(读锁):可以同时允许多个线程持有读锁,但是当有写锁被持有时,悲观读锁的获取会被阻塞等待。
  3. 写锁(排他锁):独占,只允许一个线程持有写锁,其他线程无法获取读锁或写锁。当持有写锁时,其他读线程和写线程被阻塞。
  4. 不可重入性:因为 StampedLock 的写锁是独占的,读锁是共享的。当一个线程持有写锁时,其他线程无法获取读锁,从而保证数据的一致性。同样地,当一个线程持有读锁时,其他线程无法获取写锁,以避免读取到正在被写入的数据。
  5. StampedLock 不具备公平锁特性,StampedLock 的等待队列是无序的
  6. 不支持条件变量Condition。 StampedLock本身不直接支持Condition接口的使用。如果需要在使用StampedLock时实现条件等待和通知的功能,可以结合其他的同步工具,如ReentrantLock和Condition来实现

StampedLock 的源码中,并没有直接使用 AbstractQueuedSynchronizer(AQS)类,但是 StampedLock 的实现借鉴了 AQS 的一些概念和思想。

关于多种锁的组合情况,大概如下:

  • 一个写锁和多个乐观读锁,读写是并行执行的,不会有任何堵塞。
  • 多个写锁和一到任意多个乐观读锁,写锁之间会堵塞等待,读锁正常并行。
  • 一个写锁和多个悲观读锁,如果写锁的获取是在悲观读之后,那么写锁会等待悲观读锁释放在获取。如果写锁的获取是在悲观读之前,就是悲观读锁等写锁释放了。
  • 多个写锁和一到任意多个悲观读锁,如果写锁的获取是在悲观读之后,那么写锁会等待悲观读锁释放在获取,悲观读并行执行。如果写锁的获取是在悲观读之前,就是悲观读锁等写锁释放了,悲观读依然并行执行。
  • 乐观读与悲观读一起使用,在没有写锁情况下,二者并行执行。

2.原理

1.读写锁的版本号

StampedLock 使用一个 64 位的版本号来追踪锁的状态。这个版本号由高 32 位和低 32 位组成。高 32 位用于表示写锁的版本号,而低 32 位则用于表示读锁的持有数量。

为什么是64位,因为版本号是long类型的,long类型是64位:

/** Lock sequence/state */
private transient volatile long state;

对于读锁的持有数量的管理,StampedLock 使用了版本号的低32位来存储读锁数量。当线程获取读锁时,会将当前的版本号(包含读锁的持有数量)作为锁的标记,并将读锁持有数量加一。读锁的持有数量的增加会导致版本号的变化。

对于写锁的管理,StampedLock 使用了版本号的高 32 位。写锁的版本号是递增的值的最高位取反的形式。写锁的版本号的变化只与写锁有关,与读锁的持有数量无关。

版本号的生成依赖于一个volatilestate

获取写锁

image-20231108105721519

获取读锁

image-20231108105749271

state变量

image-20231108105816886

由于版本号的操作,在StampedLock 的释放操作都需要传入版本号

StampedLock lock = new StampedLock();
long stamp = lock.writeLock();
lock.unlockWrite(stamp);

long stamp = lock.tryOptimisticRead();
lock.unlockRead(stamp);

validated的验证原理:

StampedLock 的 validate(long stamp) 方法用于验证给定的版本号(stamp)是否仍然有效。该方法的原理如下:

  1. validate() 方法接受一个版本号(stamp)作为参数,用于验证该版本号是否仍然有效。版本号可以通过调用 long readLock()long tryOptimisticRead() 方法获取。
  2. 首先,validate() 方法会将给定的版本号与当前的版本号进行比较。如果给定的版本号与当前版本号相等,表示该版本号仍然有效,可以继续操作。
  3. 如果给定的版本号与当前版本号不相等,说明在当前线程获取版本号后,可能有其他线程获取了写锁或者存在其他线程对锁进行了修改。在这种情况下,validate() 方法会返回 false,表示给定的版本号已经失效。
  4. validate() 方法返回 false 时,需要根据具体的业务逻辑来决定后续的处理方式。通常情况下,可以选择重新获取锁或者放弃当前操作。

通过调用 validate() 方法,可以判断给定的版本号是否仍然有效,从而确保在使用乐观读锁期间,没有其他线程对锁进行了修改。这一机制可以用于在使用乐观读锁时进行额外的校验,以确保数据的一致性和准确性。

2.等待队列

StampedLock 内部使用 WNode(Wait Node)来表示等待队列中的节点。WNodeStampedLock 内部的一个内部类,用于管理线程在获取写锁时的等待和唤醒过程。

WNode 包含以下字段:

  1. mode:表示节点的模式,可以是独占模式(exclusive mode)或共享模式(shared mode)。
  2. next:指向下一个节点的引用,用于构成等待队列。
  3. status:表示节点的状态,可以是等待获取锁(WAITING)、已经获取锁(OWNED),或者是取消(CANCELLED)。
  4. thread:表示节点对应的线程。
  5. exclusively:一个标志位,表示节点是否以独占模式等待获取锁。

**WNode 的主要作用是构成 StampedLock 的等待队列。**当一个线程无法获取写锁时,会创建一个 WNode 节点并加入到等待队列中。当锁的状态发生改变时,会通过唤醒机制来通知等待队列中的节点。

**在 StampedLock 中,WNode 节点是以链表形式组织的,**通过 WNode 类的 next 字段来连接各个节点,形成等待队列。而在节点被唤醒时,会按照特定的规则从队列中移除该节点。在等待队列中,节点按照先进先出的顺序进行唤醒,即先加入队列的节点先被唤醒。当锁的状态发生改变时,会按照特定的规则从队列中移除节点,并唤醒下一个节点。

WNode 的模式可以是独占模式和共享模式。

  1. 独占模式(exclusive mode): 当线程请求以独占模式获取锁(写锁)时,会创建一个以独占模式的 WNode 节点,并将其加入等待队列。在独占模式下,只有一个线程可以获得写锁,其他线程必须等待该线程释放写锁才能继续执行。
  2. 共享模式(shared mode): 当线程请求以共享模式获取锁(读锁)时,会创建一个以共享模式的 WNode 节点,并将其加入等待队列。在共享模式下,多个线程可以同时获得读锁,读锁之间不会互斥。但是当有线程持有写锁时,其他线程无法获取读锁,需要等待写锁释放。

WNode 的设计和使用是为了实现 StampedLock 的等待和唤醒机制,以及支持写锁的独占模式。通过合理管理和操作 WNode 节点,可以实现对写锁的竞争和等待的控制,确保线程安全和并发性能。

3.读写锁的创建

写锁的创建

StampedLockwriteLock方法,内部调用acquireWrite方法获取写锁:

  • acquireWrite()方法会先尝试直接获取写锁,即检查当前是否有其他线程持有读锁或写锁。如果没有,当前线程可以直接获取写锁并返回一个非零的stamp。这是一种乐观的锁获取方式,无需进行线程等待。

  • 如果当前有其他线程持有读锁或写锁,会使用WNode队列来进行阻塞等待。此时,会创建一个以独占模式的WNode节点,并将当前线程加入等待队列。当前线程需要等待这些锁被释放才能获取写锁。

悲观读锁的创建

StampedLockreadLock方法,内部调用acquireRead()方法用于获取读锁,其主要作用是阻塞当前线程,直到成功获取到读锁为止。

  • acquireRead方法会尝试直接获取读锁,即检查当前是否有其他线程持有写锁。如果没有,当前线程可以直接获取读锁并返回一个非零的stamp。

  • 如果当前有其他线程持有写锁,会使用WNode队列来进行阻塞等待。此时,会创建一个以**共享模式的WNode节点(多个线程可以同时获得读锁,读锁之间不会互斥),并将其加入等待队列。这样的场景通常发生在有其他线程持有写锁**时,当前线程需要等待写锁被释放才能获取读锁。

乐观读锁的创建

StampedLocktryOptimisticRead方法

乐观读锁是一种非阻塞的锁获取方式,它通过读取**版本号**(stamp)来判断数据是否被写锁修改过。当线程获取乐观读锁时,只会读取当前的版本号,并将其保存在一个局部变量中。

  • 如果在之后的读取操作中发现版本号没有变化,则可以认为数据没有被写锁修改过,读取操作可以继续进行。
  • 如果在乐观读锁期间发生了写锁的获取,写锁会对版本号进行更新,使得之前保存的版本号变得无效。此时,乐观读锁会失效,需要再次尝试获取读锁或者转换为悲观读锁。

因为乐观读锁是一种快速路径,在其创建过程中并没有使用WNode队列

乐观读锁不需要显示释放,没有释放方法

4.代码示例

1.悲观读使用

在 move 方法中,我们使用写锁来更新 x 和 y 的值。通过调用 lock.writeLock() 获取写锁,并在操作完成后调用 lock.unlockWrite() 来释放写锁。

distanceFromOrigin 会读取x与y的值,与 move 并行执行,move负责写,distanceFromOrigin负责读。

在 distanceFromOrigin 方法中,我们首先尝试使用lock.tryOptimisticRead()获取乐观读锁读取 x 和 y 的值。并使用lock.validate方法对读锁进行验证。

验证的目的是因为:

  • 读写是同时进行的,读到值的同时,该值也可能在被更改,导致读到的值不准确。
  • 如果validate方法为true,则证明读的时候没有写锁被持有,也就是读时没有写操作。
  • 如果为false,则证明读时有写操作在运行,需要获取悲观读锁读取,来保证准确性。
import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private double x, y;
    private final StampedLock lock = new StampedLock();

    //对x、y进行写操作
    public void move(double deltaX, double deltaY) {
        Thread write = new Thread(() -> {
            //获取写锁
            long stamp = lock.writeLock();
            try {
                Thread.sleep(1000);
                x += deltaX;
                y += deltaY;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放写锁,需要stamp
                lock.unlockWrite(stamp);
            }
        });
        write.start();
    }

    //对x、y进行读操作
    public void distanceFromOrigin() {
        Thread write = new Thread(() -> {
            //获取乐观读锁
            long stamp = lock.tryOptimisticRead();
            //用乐观读锁读取x、y
            double currentX = x;
            double currentY = y;

            //validate用于验证乐观读锁的有效性
            if (!lock.validate(stamp)) {
                //如无效就获取悲观读锁
                stamp = lock.readLock();
                try {
                    System.out.println("悲观读锁被获取");
                    //用悲观读,读取x、y的值
                    currentX = x;
                    currentY = y;
                } finally {
                    lock.unlockRead(stamp);
                }
            }
            System.out.println("x: "+currentX+", y: "+currentY);
        });
        write.start();
    }

    public static void main(String[] args) {
        StampedLockExample example = new StampedLockExample();
        example.move(3, 4);
        example.distanceFromOrigin();
    }
}

结果:

悲观读锁被获取
x: 3.0, y: 4.0

由于写方法move中睡眠了1秒,执行distanceFromOrigin读方法时,因为有写操作进行(写锁被持有),所以validate验证不通过为false

使用悲观锁并输出了"悲观读锁被获取"。

如果去掉线程睡眠1秒,就不会使用悲观锁了,结果就是:

x: 3.0, y: 4.0
  • 悲观读的目的,就是保证读取数据的准确性。

  • 乐观读目的,就是提升在没有写时的读取效率。

2.悲观读与写的互斥

验证效果

1.如果写锁的获取是在悲观读之后,那么写锁会等待悲观读锁释放在获取。

2.如果写锁的获取是在悲观读之前,那么悲观读锁会等待写锁释放在获取。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private double x = 2.0, y=3.0;
    private final StampedLock lock = new StampedLock();

    //对x、y进行写操作
    public void move(double deltaX, double deltaY) {
        Thread write = new Thread(() -> {
            long stamp = lock.writeLock();
            try {
                Thread.sleep(2000);
                System.out.println("写锁时间是"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

                x += deltaX;
                y += deltaY;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlockWrite(stamp);
            }
        });
        write.start();
    }

    //对x、y进行读操作
    public void distanceFromOrigin() {
        Thread write = new Thread(() -> {

                //如无效就获取悲观读锁
                long stamp = lock.readLock();
                try {
                    Thread.sleep(1500);
                    System.out.println("悲观读锁1被获取");
                    System.out.println("悲观1时间是"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    System.out.println("x: "+x+", y: "+y);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlockRead(stamp);
                }

        });
        write.start();
    }

    //对x、y进行读操作
    public void distanceFromOrigin1() {
        Thread write = new Thread(() -> {

                //如无效就获取悲观读锁
                long stamp = lock.readLock();
                try {
                    Thread.sleep(2000);
                    System.out.println("悲观读锁2被获取");
                    System.out.println("悲观2时间是"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    System.out.println("x: "+x+", y: "+y);
                    //用悲观读,读取x、y的值

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlockRead(stamp);
                }

        });
        write.start();
    }

    public static void main(String[] args) {
        StampedLockExample example = new StampedLockExample();
        example.distanceFromOrigin();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        example.move(3.0,4.0);
        example.distanceFromOrigin1();
    }
}

结果:

写锁执行在悲观读锁2秒后

悲观读锁1被获取
悲观1时间是2023-11-08 14:00:49
x: 2.0, y: 3.0

写锁时间是2023-11-08 14:00:51

悲观读锁2被获取
悲观2时间是2023-11-08 14:00:53
x: 5.0, y: 7.0

如果删除掉主方法中的example.distanceFromOrigin()

public static void main(String[] args) {
        StampedLockExample example = new StampedLockExample();
        example.move(3.0,4.0);
        example.distanceFromOrigin1();
}

结果:

那就是先执行写锁在执行悲观读了

写锁时间是2023-11-08 14:22:35
悲观读锁2被获取
悲观2时间是2023-11-08 14:22:37
x: 5.0, y: 7.0
4.常用方法
1.尝试获取锁

与直接获取的区别是不会等待

尝试获取写锁,如果写锁当前没有被其他线程持有,则返回一个非零的stamp值作为锁的票据,表示成功获取写锁。如果写锁已经被其他线程持有,tryWriteLock()方法将立即返回0,表示获取写锁失败。

//获取写
tryWriteLock()
tryWriteLock(long time, TimeUnit unit)

//获取悲观读
tryReadLock()
tryReadLock(long time, TimeUnit unit)
2.可中断获取锁方法

用于获取锁并允许响应中断。

//获取写
writeLockInterruptibly()

//获取悲观读
readLockInterruptibly
3.读锁转换为写锁

StampedLock的tryConvertToWriteLock方法

import java.util.concurrent.locks.StampedLock;

public class Main {

    public static void main(String[] args) {
        StampedLock lock = new StampedLock();

        long stamp = lock.readLock();
        System.out.println("Read lock acquired");
		
        //转换方法
        long writeStamp = lock.tryConvertToWriteLock(stamp);
        
        //邮戳不等于0则证明转换成功
        if (writeStamp != 0) {
            System.out.println("Read lock converted to write lock");
            lock.unlockWrite(writeStamp);
        } else {
            System.out.println("Unable to convert read lock to write lock");
            lock.unlockRead(stamp);
        }
    }
}

在上面的示例中,我们创建了一个StampedLock实例,并获取了一个读锁。然后我们使用tryConvertToWriteLock()方法尝试将读锁转换为写锁,并根据返回的标记值判断转换是否成功。如果转换成功,我们输出一条成功的消息并释放写锁。如果转换失败,我们输出一条失败的消息并释放读锁。

注意:StampedLock是在Java 8中引入的,并且在一些特定的场景中,比如在读多写少的情况下,它可以提供更好的性能。但是,使用StampedLock需要注意它的使用限制和适用场景。

4.写锁转换为读锁

StampedLock的tryConvertToReadLock()方法尝试将已经获取的写锁转换为读锁。

需要传入之前获取到的写锁的stamp作为参数。如果转换成功,即写锁成功降级为读锁,tryConvertToReadLock()方法返回true。如果转换失败,即当前线程没有访问权限或锁被其他写线程持有,tryConvertToReadLock()方法返回false。

需要注意的是,tryConvertToReadLock()方法是一个非阻塞的操作。无论是否成功将写锁转换为读锁,都不会导致线程阻塞等待。如果转换成功,后续的代码可以执行读操作。如果转换失败,可以根据需要继续执行写操作或其他操作。

在使用tryConvertToReadLock()方法时,需要注意确保写锁的stamp值是有效的,并且在适当的时候释放读锁和写锁,以避免死锁和资源泄漏。

public static void main(String[] args) {

        StampedLock lock = new StampedLock();

        long stamp = lock.writeLock();
        System.out.println("Write lock acquired");

        long readStamp = lock.tryConvertToReadLock(stamp);
        //邮戳不等于0则证明转换成功
        if (readStamp != 0) {
            System.out.println("Write lock converted to Read lock");
            lock.unlockRead(readStamp);
        } else {
            System.out.println("Unable to convert Write lock to read lock");
            lock.unlockRead(stamp);
        }

}
5.转乐观读锁

写锁或悲观读锁转换为乐观读锁

StampedLock类提供了tryConvertToOptimisticRead(long stamp)方法,用于尝试将写锁或悲观读锁转换为乐观读锁。该方法的签名如下:

boolean tryConvertToOptimisticRead(long stamp)

tryConvertToOptimisticRead()方法尝试将已经获取的写锁或悲观读锁转换为乐观读锁。需要传入之前获取到的写锁或悲观读锁的stamp作为参数。如果转换成功,即锁成功转换为乐观读锁,tryConvertToOptimisticRead()方法返回true。如果转换失败,即当前线程没有访问权限或锁被其他写线程持有,tryConvertToOptimisticRead()方法返回false。

需要注意的是,tryConvertToOptimisticRead()方法是一个非阻塞的操作。无论是否成功将锁转换为乐观读锁,都不会导致线程阻塞等待。如果转换成功,后续的代码可以执行乐观读操作。如果转换失败,可以根据需要继续执行写操作或悲观读操作。

在使用tryConvertToOptimisticRead()方法时,需要注意确保写锁或悲观读锁的stamp值是有效的,并且在适当的时候释放锁资源,以避免死锁和资源泄漏。此外,还需要使用tryOptimisticRead()方法来进行乐观读操作,并在需要的时候进行验证。

6.返回当前持有读锁的线程数量

StampedLock类的getReadLockCount()方法返回当前持有读锁的线程数量。

这个方法只是一个估计值,因为在获取数量时,其他线程可能会动态地获取或释放读锁。

public int getReadLockCount()

StampedLock类的getReadLockCount(long s)方法返回给定标记值之后,持有该标记值的线程数量。这个方法用于检查在给定标记值之后,是否有其他线程持有读锁。

public int getReadLockCount(long s)
7.检查是否有线程持有锁

有返回true,没有返回false

检查是否有线程持有写锁

public boolean isWriteLocked()

检查是否有线程持有读锁

public boolean isReadLocked()
8.获取读写锁实例

StampedLock类的asReadLock()方法返回一个ReadLock接口的实例,用于获取读锁。该读锁可以通过调用ReadLock接口的相关方法来获取和释放。

方法签名如下:

public ReadLock asReadLock()

StampedLock类的asWriteLock()方法返回一个WriteLock接口的实例,用于获取写锁。该写锁可以通过调用WriteLock接口的相关方法来获取和释放。

方法签名如下:

public WriteLock asWriteLock()

以下是一个示例代码:

import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.locks.StampedLock.ReadLock;
import java.util.concurrent.locks.StampedLock.WriteLock;

public class Main {

    public static void main(String[] args) {
        StampedLock lock = new StampedLock();

        ReadLock readLock = lock.asReadLock();
        WriteLock writeLock = lock.asWriteLock();

        readLock.lock();
        System.out.println("Read lock acquired");

        writeLock.lock();
        System.out.println("Write lock acquired");

        writeLock.unlock();
        System.out.println("Write lock released");

        readLock.unlock();
        System.out.println("Read lock released");
    }
}

在上面的示例中,我们创建了一个StampedLock实例,并使用asReadLock()方法和asWriteLock()方法分别获取了一个ReadLock实例和一个WriteLock实例。然后,我们使用这两个锁分别获取和释放了读锁和写锁。

需要注意的是,使用asReadLock()方法和asWriteLock()方法获取锁实例后,可以通过调用锁实例所实现的接口的方法来获取和释放锁。这样可以更加方便地进行锁操作。

9.获取读写锁ReadWriteLock

StampedLock类的asReadWriteLock方法返回一个ReadWriteLock视图,该视图支持读锁和写锁操作。通过这个视图,可以使用读锁和写锁进行并发控制。

具体来说,asReadWriteLock方法返回一个接口类型为ReadWriteLock的视图对象。可以使用该视图对象调用readLock()方法获取读锁,并使用writeLock()方法获取写锁。获取读锁和写锁后,可以执行相应的读操作和写操作。

示例代码如下所示:

StampedLock lock = new StampedLock();
ReadWriteLock readWriteLock = lock.asReadWriteLock();

readWriteLock.readLock().lock();
try {
    // 执行读操作
} finally {
    readWriteLock.readLock().unlock();
}

readWriteLock.writeLock().lock();
try {
    // 执行写操作
} finally {
    readWriteLock.writeLock().unlock();
}

需要注意的是,StampedLock类是Java 8中引入的一种新的锁机制,它提供了乐观读锁和悲观读锁的支持。通过StampedLock类,可以实现更高效的读写操作,并且在多线程环境下提供更好的性能。

ToOptimisticRead()方法尝试将已经获取的写锁或悲观读锁转换为乐观读锁。需要传入之前获取到的写锁或悲观读锁的stamp作为参数。如果转换成功,即锁成功转换为乐观读锁,tryConvertToOptimisticRead()方法返回true。如果转换失败,即当前线程没有访问权限或锁被其他写线程持有,tryConvertToOptimisticRead()`方法返回false。

需要注意的是,tryConvertToOptimisticRead()方法是一个非阻塞的操作。无论是否成功将锁转换为乐观读锁,都不会导致线程阻塞等待。如果转换成功,后续的代码可以执行乐观读操作。如果转换失败,可以根据需要继续执行写操作或悲观读操作。

在使用tryConvertToOptimisticRead()方法时,需要注意确保写锁或悲观读锁的stamp值是有效的,并且在适当的时候释放锁资源,以避免死锁和资源泄漏。此外,还需要使用tryOptimisticRead()方法来进行乐观读操作,并在需要的时候进行验证。

6.返回当前持有读锁的线程数量

StampedLock类的getReadLockCount()方法返回当前持有读锁的线程数量。

这个方法只是一个估计值,因为在获取数量时,其他线程可能会动态地获取或释放读锁。

public int getReadLockCount()

StampedLock类的getReadLockCount(long s)方法返回给定标记值之后,持有该标记值的线程数量。这个方法用于检查在给定标记值之后,是否有其他线程持有读锁。

public int getReadLockCount(long s)
7.检查是否有线程持有锁

有返回true,没有返回false

检查是否有线程持有写锁

public boolean isWriteLocked()

检查是否有线程持有读锁

public boolean isReadLocked()
8.获取读写锁实例

StampedLock类的asReadLock()方法返回一个ReadLock接口的实例,用于获取读锁。该读锁可以通过调用ReadLock接口的相关方法来获取和释放。

方法签名如下:

public ReadLock asReadLock()

StampedLock类的asWriteLock()方法返回一个WriteLock接口的实例,用于获取写锁。该写锁可以通过调用WriteLock接口的相关方法来获取和释放。

方法签名如下:

public WriteLock asWriteLock()

以下是一个示例代码:

import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.locks.StampedLock.ReadLock;
import java.util.concurrent.locks.StampedLock.WriteLock;

public class Main {

    public static void main(String[] args) {
        StampedLock lock = new StampedLock();

        ReadLock readLock = lock.asReadLock();
        WriteLock writeLock = lock.asWriteLock();

        readLock.lock();
        System.out.println("Read lock acquired");

        writeLock.lock();
        System.out.println("Write lock acquired");

        writeLock.unlock();
        System.out.println("Write lock released");

        readLock.unlock();
        System.out.println("Read lock released");
    }
}

在上面的示例中,我们创建了一个StampedLock实例,并使用asReadLock()方法和asWriteLock()方法分别获取了一个ReadLock实例和一个WriteLock实例。然后,我们使用这两个锁分别获取和释放了读锁和写锁。

需要注意的是,使用asReadLock()方法和asWriteLock()方法获取锁实例后,可以通过调用锁实例所实现的接口的方法来获取和释放锁。这样可以更加方便地进行锁操作。

9.获取读写锁ReadWriteLock

StampedLock类的asReadWriteLock方法返回一个ReadWriteLock视图,该视图支持读锁和写锁操作。通过这个视图,可以使用读锁和写锁进行并发控制。

具体来说,asReadWriteLock方法返回一个接口类型为ReadWriteLock的视图对象。可以使用该视图对象调用readLock()方法获取读锁,并使用writeLock()方法获取写锁。获取读锁和写锁后,可以执行相应的读操作和写操作。

示例代码如下所示:

StampedLock lock = new StampedLock();
ReadWriteLock readWriteLock = lock.asReadWriteLock();

readWriteLock.readLock().lock();
try {
    // 执行读操作
} finally {
    readWriteLock.readLock().unlock();
}

readWriteLock.writeLock().lock();
try {
    // 执行写操作
} finally {
    readWriteLock.writeLock().unlock();
}

需要注意的是,StampedLock类是Java 8中引入的一种新的锁机制,它提供了乐观读锁和悲观读锁的支持。通过StampedLock类,可以实现更高效的读写操作,并且在多线程环境下提供更好的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值