JUC总体结构
JUC(Java.util.concurrent)包结构如下:
- 并发集合(Concurrent Collections):这些类提供了线程安全的集合类,用于在多线程环境下进行并发操作。
- ConcurrentHashMap:线程安全的哈希表实现。
- ConcurrentLinkedDeque:线程安全的双端队列实现。
- ConcurrentLinkedQueue:线程安全的队列实现。
- ConcurrentSkipListMap:线程安全的跳表实现的有序映射表。
- ConcurrentSkipListSet:线程安全的跳表实现的有序集合。
- CopyOnWriteArrayList:线程安全的动态数组实现。
- CopyOnWriteArraySet:线程安全的动态数组实现的集合。
- 同步器(Synchronizers):这些类提供了用于控制多个线程之间的协调和同步的工具。
- CountDownLatch:一个线程等待其他线程完成一定数量的操作后再继续执行。
- CyclicBarrier:一组线程互相等待,直到所有线程都达到一个屏障点后再继续执行。
- Semaphore:控制同时访问某个资源的线程数量。
- Exchanger:两个线程之间进行数据交换。
- 原子类(Atomic Variables):这些类提供了原子操作,以确保在多线程环境下的数据安全性。
- AtomicInteger:原子操作的int类型。
- AtomicLong:原子操作的long类型。
- AtomicBoolean:原子操作的boolean类型。
- AtomicReference:原子操作的引用类型。
- 锁框架(Lock Framework):这些类提供了更灵活和可扩展的锁机制。
- Lock:一个互斥锁的抽象。
- ReentrantLock:可重入锁的实现。
- ReadWriteLock:读写锁的抽象。
- ReentrantReadWriteLock:读写锁的实现。
- 带有执行结果的线程:可用于异步回调。
- Callable
- CompletableFuture
- Future
- 线程池(Executor Framework):这些类提供了管理和调度线程池的功能。
- Executor:执行任务的相关接口。
- ExecutorService:扩展了Executor接口,支持生命周期管理和返回Future对象。
- ThreadPoolExecutor:线程池的实现。
- ScheduledExecutorService:支持任务调度的扩展接口。
- 定时任务(Scheduled Tasks):这些类用于执行定时任务和周期性任务。
- ScheduledExecutorService:支持任务调度的扩展接口。
- ScheduledThreadPoolExecutor:定时任务的实现。
- 并发类的辅助工具:这些类提供了一些辅助功能,用于帮助并发编程。
- Phaser:一个栅栏,用于同步多个线程的执行。
- Exchanger:两个线程之间进行数据交换。
- CompletionService:将任务的执行结果异步返回。
- 原子数组(Atomic Arrays):这些类提供了原子操作的数组类型。
- AtomicIntegerArray:原子操作的int数组。
- AtomicLongArray:原子操作的long数组。
- AtomicReferenceArray:原子操作的引用类型数组。
- 可见性(Visibility):这些类和接口提供了一些可见性的保证。
- volatile关键字:用于确保变量的可见性。
- AtomicBoolean:原子操作的boolean类型。
锁简介
锁在Java线程中的作用是用于控制多个线程对共享资源的访问。在多线程环境下,当多个线程同时访问共享资源时,可能会导致数据不一致或竞态条件的问题。锁的使用可以确保在同一时间只有一个线程能够访问共享资源,从而避免并发访问引起的问题。
在JUC(Java.util.concurrent)包中,Lock接口有几个常用的实现类,它们分别是:
ReentrantLock
:可重入锁。- ReentrantLock是Lock接口的主要实现类,它提供了与synchronized关键字类似的功能,但更加灵活和可扩展。
- ReentrantLock支持可重入性,即同一个线程可以多次获取锁。
- ReentrantLock还提供了各种高级功能,如公平性选择、条件变量等。
ReentrantReadWriteLock
:读写锁。- ReentrantReadWriteLock是Lock接口的另一个实现类,它提供了读写分离的锁机制。
- ReentrantReadWriteLock允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。
- 读锁是共享锁,写锁是独占锁。
StampedLock
:戳锁。- StampedLock是JDK8引入的新的锁机制,它提供了一种乐观读锁的机制。
- StampedLock允许读锁和写锁之间的转换,提供更高的并发性能。
这些Lock的实现类提供了更灵活、更高级的锁机制,可以替代传统的synchronized关键字。通过使用这些锁,我们可以更好地控制线程的访问和并发操作,确保多线程环境下的数据安全性和性能优化。选择使用哪种锁取决于具体的需求和场景。
传统synchronized
synchronized
是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
-
修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
-
修饰一个类,其作用的范围是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
继承而来
-
状态维护:ReentrantLock内部维护了一个
state
变量,表示当前锁的状态。当线程首次获取锁时,state会被设置为1,表示线程持有锁。当同一个线程再次获取锁时,state会递增。当线程释放锁时,state
递减。只有当state
为0时,表示锁没有被任何线程持有。 -
获取锁:当一个线程尝试获取锁时,首先会判断
state
是否为0。如果是0,则表示锁没有被任何线程持有,当前线程可以获取锁,并将state
设置为1。如果state
不为0,则表示锁已经被其他线程持有。在这种情况下,线程会被阻塞,直到锁被释放,或者当前线程被中断。 -
可重入性
:ReentrantLock支持线程的可重入性。ReentrantLock内部维护了一个owner
线程和一个lock count
变量。当一个线程第一次获取锁时,lock count递增,然后线程可以多次获取锁,每次获取锁时lock count都会递增。当线程释放锁时,lock count递减,直到lock count减为0时,锁完全释放,其他线程才能获取该锁 -
等待队列
:公平锁维护了一个等待队列,用于存放等待获取锁的线程。当一个线程尝试获取锁时,如果锁已经被其他线程持有,那么线程会被放入等待队列中。公平状态下:等待队列是一个FIFO(先进先出)队列,即先到先得。 非公平状态下:等待队列是一个无序队列。 AQS的队列是从队尾开始放元素,也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。
-
公平锁
:如果构造ReentrantLock时指定了公平锁(通过ReentrantLock(true)
),线程会被放入等待队列中,按照先到先得的顺序等待获取锁。当锁释放时,等待队列中的下一个线程会被唤醒并尝试获取锁 -
非公平锁
:如果构造ReentrantLock时指定了非公平锁(通过ReentrantLock(false)
,或者使用默认构造函数),线程会直接尝试获取锁,而不会进入等待队列。如果获取锁失败,则会采用一定的策略进行重试,可能会插队获取锁。 -
锁的释放:当一个线程释放锁时,会将state递减。如果state为0,则表示锁已经完全释放,其他等待获取锁的线程有机会获取锁。
AQS
AQS
是指AbstractQueuedSynchronizer抽象类,它是Java并发包中用于实现同步器的基础框架,提供了一种同步机制,可用于构建各种同步类,如ReentrantLock、CountDownLatch、Semaphore等。
AQS
的主要作用是提供了一种基于FIFO(先进先出)等待队列的同步机制,通过内置的同步状态(state)和等待队列来实现线程的等待/唤醒机制。
ReentrantLock
是通过继承AQS
来实现可重入锁和公平锁的。AQS
为ReentrantLock
提供了支持,通过继承和重写AQS
的方法,ReentrantLock
实现了自己的获取锁和释放锁的逻辑。
- 对于可重入锁(ReentrantLock,默认情况下为非公平锁),AQS中维护了一个持有锁的线程对象(owner)和一个重入次数(holdCount),通过这些信息来判断当前线程是否可以再次获取锁。
- 对于公平锁,AQS中的等待队列(CLH队列)以及相关的线程状态(Node)被用来维护等待获取锁的线程顺序,确保先到先得的获取锁顺序。
通过继承AQS
,ReentrantLock
实现了对这些状态和逻辑的管理和控制,从而实现了可重入锁和公平锁的功能。
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
**
5.条件变量
ReentrantLock
提供了条件变量(Condition
)的支持,通过Condition
对象可以实现线程间的等待和唤醒机制。条件变量可以更加灵活地控制线程的等待和唤醒,以实现更复杂的线程同步操作
条件变量的实现需要Condition接口与Lock接口配套使用,用于实现线程的等待和唤醒。它提供了和Object类中的wait()、notify()和notifyAll()方法类似的功能,但相比之下更加灵活和可控。
Condition
接口定义了以下几个常用的方法:
await()
:使当前线程进入等待状态,直到接收到信号或被中断。调用await()方法后,会释放当前线程持有的锁,并且进入等待状态。signal()
:唤醒一个等待在该Condition上的线程。调用signal()方法时,会从等待队列中选择一个线程,并使其进入可运行状态。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.可中断锁获取的方法
ReentrantLock的
lockInterruptibly()
方法是一个可中断的获取锁的方法。它是Lock接口中的一个方法,用于获取锁并允许响应中断。
当线程A调用lockInterruptibly()
方法时,会尝试获取锁。如果锁可用(没有其他线程持有锁),线程A将立即获取锁,并且可以继续执行后续代码。
如果锁不可用,即其他线程持有了锁并且未释放,线程A将被阻塞。被阻塞的线程A会进入一个等待状态,直到满足以下两个条件之一:
- 其他线程释放了锁,线程A成功获取到了锁,然后它可以继续执行后续代码。
- 线程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();
}
}
运行结果:
过程解析:
- 主线程通过
lock.lock()
先获得锁。 - 然后启动thread线程,由于主线程
Thread.sleep(1000)
睡了1秒,会先调用thread的lock.lockInterruptibly()
,尝试获得锁,但是锁在主线程手里,thread获取不到,进入阻塞状态。 - 然后主线程执行
thread.interrupt()
,thread线程被中断,并抛出异常结束方法,由于return
,"t1线程获得了锁"并没有打印控制台
2.尝试获取锁的方法
ReentrantLock的
tryLock()
方法是尝试获取锁的方法,它会立即返回一个布尔值来指示是否成功获取锁。
当线程调用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.获取持有锁次数
ReentrantLock的
getHoldCount()
方法是用于获取当前线程持有锁的次数的方法。
当线程调用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.判断锁是否被当前线程持有
ReentrantLock的
isHeldByCurrentThread()
方法是用于判断当前线程是否持有该锁的方法。
当线程调用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.判断锁是否被任意线程持有
ReentrantLock的
isLocked()
方法是用于判断锁是否被任何线程持有的方法。
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.判断锁是不是公平锁
ReentrantLock的
isFair()
方法是用于判断锁是否是公平锁的方法。
公平锁是一种锁获取的机制,它确保锁的获取按照线程的申请顺序进行。当锁被释放时,等待时间最长的线程将获得锁的访问权。而非公平锁则没有这种保证,可能会导致等待时间较短的线程获取到锁。
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.判断是否有线程在队列中等待获取锁
ReentrantLock的
hasQueuedThreads()
方法,是用于判断是否有线程正在等待获取锁的方法。与上面不同的是它无法指定线程判断
方法签名: 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.返回正在等待获取锁的线程数
ReentrantLock的
getQueueLength
方法是用于获取等待获取锁的线程数的方法
方法签名: 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.判断是否有线程在等待某个特定条件
ReentrantLock的
hasWaiters()
方法是用于判断是否有线程在等待某个特定条件的方法
方法签名: 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.获取某个条件等待锁的线程数
ReentrantLock的
getWaitQueueLength
方法是用于获取等待与此锁关联的给定条件的线程数的方法。
方法签名: 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.获取等待某个条件的线程列表
ReentrantLock的
getWaitingThreads(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
的特点如下:
- 读锁(共享锁):多个线程可以同时获得读锁,只要没有线程持有写锁。读锁在没有写锁的情况下是共享的,因此可以提高并发性能。
- 写锁(排他锁):只有一个线程可以获得写锁。当一个线程持有写锁时,其他线程无法获得读锁或写锁。
- 重入性:与 ReentrantLock 一样,ReentrantReadWriteLock 也是可重入的,同一个线程可以多次获取读锁或写锁。
- 公平与非公平:与 ReentrantLock 一样,ReentrantReadWriteLock 可以通过构造方法指定公平性
- 条件变量:
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
的公平锁实现一致。
而且内部同样是维护了等待队列:
- 当一个线程请求锁但锁已被其他线程持有时,该线程会被放入等待队列中(末尾)
- 等待队列中的线程按照请求的先后顺序进行排列,即先进入等待队列的线程会先获得锁
- 当锁的持有者释放锁的时候,ReentrantReadWriteLock会从等待队列中选择一个线程分配锁
- 而公平锁会选择等待队列中最先入队的线程,将锁分配给它
原理
在ReentrantReadWriteLock
中,有三个内部类
- Sync
- ReadLock
- WriteLock
读写锁的核心实现,来源与
AQS
的volatile
整数变量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来实现
2.读锁
ReadLock,使用
sync
的acquireShared
方法
acquireShared
方法由AQS
继承而来,主要作用是尝试获取共享锁
,如果获取成功,则方法会立即返回;否则,线程会被加入到等待队列中,并在获取到共享锁时被唤醒。
具体来说,
acquireShared
()方法的内部实现如下:
- 首先,该方法会调用
tryAcquireShared
()方法尝试获取共享锁。tryAcquireShared
()方法是一个抽象方法,需要由具体的同步器类(如ReentrantReadWriteLock)实现。如果tryAcquireShared()返回一个大于等于0的值,表示获取共享锁成功;如果返回一个小于0的值,表示获取共享锁失败。 - 如果
tryAcquireShared
()返回大于等于0的值,表示获取共享锁成功,acquireShared()方法会直接返回。 - 如果
tryAcquireShared
()返回小于0的值,表示获取共享锁失败,线程会被加入到等待队列中,并进入等待状态。 - 当
unlock
释放共享锁时,因为ReentrantReadWriteLock
是悲观读,会调用releaseShared
()方法来唤醒等待队列中的一个或多个线程。被唤醒的线程会再次尝试获取共享锁,直到成功获取为止。
acquireShared
内部有两个方法
2.1尝试获取共享锁
tryAcquireShared
尝试获取共享锁
首先,会调用tryAcquireShared
()方法尝试获取共享锁。tryAcquireShared
()方法是一个抽象方法,需要由具体的同步器类(这里是ReentrantReadWriteLock)实现。如果tryAcquireShared
()返回一个大于等于0的值,表示获取共享锁成功;如果返回一个小于0的值,表示获取共享锁失败
下面是尝试获取共享锁的步骤:
1.先验证是否存在写锁
这里通过调用exclusiveCount(c)
方法来检查是否存在排他锁。如果exclusiveCount(c)
的返回值不为0(表示存在排他锁),并且通过getExclusiveOwnerThread() != current
获取到的排他锁的拥有线程不是当前线程,那么就说明存在写锁并且不属于当前线程,此时会返回-1,表示获取共享锁失败。
exclusiveCount
方法通过位运算来检查是否存在排他锁。以下是代码解释:
EXCLUSIVE_MASK`是一个常量,用于提取`state`中排他锁计数器的位。在`ReentrantReadWriteLock`中,`state`的高16位用于存储共享锁计数器,低16位用于存储排他锁计数器。
`exclusiveCount()`方法中的位运算使用了按位与操作符`&`,它可以将`state`中的低16位全部置为0,只保留高16位的值。这样做的效果就是提取出了`state`中排他锁计数器的值。
因此,`exclusiveCount()`方法返回的值就是`state`中的排他锁计数器的值。如果返回的结果不为0,就表示存在排他锁;如果返回的结果为0,表示不存在排他锁。通过这个方法,可以检查锁的状态中是否存在排他锁。
关于getExclusiveOwnerThread() != current
,为什么还要检查获取的排他锁的拥有线程是不是当前线程,原因是对于ReentrantReadWriteLock
,它允许一个线程同时拥有读锁和写锁,这种情况下不会产生互斥。
这种设计决策的原因是:读锁和写锁之间的互斥是在不同的线程之间保持的,而对于同一个线程来说,读锁和写锁之间是允许重入的。这样可以提高并发性能,允许多个线程同时读取共享资源,同时在需要修改共享资源时,写锁会阻止其他线程的读锁获取。
2.检查共享锁计数器值并更新
下图红框验证了state
计数器:
首先,它检查当前共享锁的数量r
是否小于最大允许数量MAX_COUNT
,以确保没有超过共享锁的上限。然后,它调用compareAndSetState(c, c + SHARED_UNIT)
方法来尝试原子地将state
的值从c
更新为c + SHARED_UNIT
。如果更新成功,表示获取共享锁成功,会执行相关的操作,如更新计数、设置第一个读取线程等。并结束方法return一个1,获取所成功。如果更新不成功,则会进入fullTryAcquireShared(current)
方法进行完整的获取共享锁操作。
2.2从等待队列获取锁
利用无限for循环,不断从队列第一个节点拿去线程去尝试获取锁,取到就结束循环返回,取不到就继续循环获取
需要注意的是,AQS中的等待队列使用的是CLH
(Craig, Landin, and Hagersten)队列算法,它是一种基于链表的队列
,能够高效地实现线程的等待和唤醒。在CLH队列中,新的等待线程是从队尾开始放置的。也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。
3.写锁
WriteLock,使用
sync
的acquire
方法
acquire
方法由AQS
继承而来,主要作用是尝试获取独占锁
,如果获取成功,则方法会立即返回;否则,线程会被加入到等待队列中,并在获取到独占锁时被唤醒。
具体来说,
acquire
()方法的实现如下:
- 首先,该方法会调用tryAcquire()方法尝试获取独占锁。tryAcquire()方法是一个抽象方法,需要由具体的同步器类(如ReentrantLock)实现。如果tryAcquire()返回true,表示获取独占锁成功;如果返回false,表示获取独占锁失败。
- 如果tryAcquire()返回true,表示获取独占锁成功,acquire()方法会直接返回。
- 如果tryAcquire()返回false,表示获取独占锁失败,线程会被加入到等待队列中,并进入等待状态。
- 当其他线程释放独占锁时,会调用release()方法来唤醒等待队列中的一个或多个线程。被唤醒的线程会再次尝试获取独占锁,直到成功获取为止。
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)
方法,用于在队列中获取锁。以下是代码逻辑的解释:
- 首先,定义一个布尔变量
failed
,初始值为true
,用于标记获取锁是否失败。 - 然后,使用
try-finally
块,保证无论获取锁是否成功,都能执行释放与处理相关的操作。 - 在
try
块中,使用无限循环for(;;)
,不断尝试获取锁。 - 在循环中,首先获取当前节点的前驱节点
p
。 - 如果前驱节点
p
是头节点(head)并且成功地通过tryAcquire(arg)
方法尝试获取锁,则将当前节点设置为头节点,断开p的next指针(帮助GC),并将failed
标记设置为false
,返回interrupted
标记,表示是否中断过线程。 - 如果上一步未能成功获取锁,则通过调用
shouldParkAfterFailedAcquire(p, node)
方法判断是否应该在获取锁失败后进行阻塞,并通过parkAndCheckInterrupt()
方法进行线程阻塞。 - 如果
parkAndCheckInterrupt()
方法返回true
,表示当前线程被中断过,将interrupted
标记设置为true
。 - 重复以上步骤,直到成功获取锁为止。
- 最后,在
finally
块中,检查是否获取锁失败,如果是,则调用cancelAcquire(node)
方法取消获取锁操作。
需要注意的是,AQS中的等待队列使用的是CLH(Craig, Landin, and Hagersten)队列算法,它是一种基于链表的队列,能够高效地实现线程的等待和唤醒。在CLH队列中,新的等待线程是从队尾开始放置的。也就是说,当一个线程需要等待时,它会被加入到等待队列的末尾,成为队尾节点。
读写锁各种方法的使用
注意:
在获取 持锁线程数、线程持锁次数时,一般都是估计值不是百分百准确,因为并发中,可能在获取到数值得同时又有新线程持有锁
1.判断当前锁是否被写线程独占持有
ReentrantReadWriteLock的
isWriteLocked
()方法是用于判断当前锁是否被写线程独占持有的方法。
具体解释如下:
- 方法定义:
- boolean isWriteLocked()
- 方法说明:
- isWriteLocked()方法用于判断当前
ReentrantReadWriteLock
是否被写线程独占持有。 - 如果当前存在一个线程持有写锁,则返回true,否则返回false。
- isWriteLocked()方法用于判断当前
- 使用场景:
- 通过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
通过调用ReentrantReadWriteLock
的isWriteLocked
()方法,可以方便地判断当前是否存在写线程独占持有锁,从而进行相应的处理。
2.获取当前读锁的持有线程数
ReentrantReadWriteLock的
getReadLockCount
()方法用于获取当前读锁的持有数。
具体解释如下:
- 方法定义:
- int
getReadLockCount
()
- int
- 方法说明:
- getReadLockCount()方法用于获取当前
ReentrantReadWriteLock
中读锁的持有数。 - 返回值是一个int类型的数值,表示当前读锁的持有数。
- getReadLockCount()方法用于获取当前
- 使用场景:
- 可以通过
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.判断当前线程是否独占持有写锁
ReentrantReadWriteLock的
isWriteLockedByCurrentThread
()方法是用于判断当前线程是否独占持有写锁的方法。
具体解释如下:
- 方法定义:
- boolean
isWriteLockedByCurrentThread
()
- boolean
- 方法说明:
isWriteLockedByCurrentThread
()方法用于判断当前线程是否独占持有写锁。- 如果当前线程持有写锁,则返回true,否则返回false。
- 使用场景:
- 可以在代码中使用
isWriteLockedByCurrentThread
()方法来判断当前线程是否持有写锁,根据结果执行相应的逻辑。 - 在写入操作之前,可以使用
isWriteLockedByCurrentThread
()方法来判断是否已经持有写锁,避免重入写锁导致的死锁。
- 可以在代码中使用
需要注意的是,isWriteLockedByCurrentThread
()方法只能判断当前线程是否独占持有写锁,并不能判断是否有其他线程在等待写锁。如果需要判断是否有其他线程在等待写锁,可以使用ReentrantReadWriteLock
的getQueuedWriterThreads
()方法。
示例代码如下:
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.获取当前线程持有写锁的次数
ReentrantReadWriteLock的
getWriteHoldCount
()方法是用于获取当前线程持有写锁的次数的方法
具体解释如下:
- 方法定义:
- int getWriteHoldCount()
- 方法说明:
- getWriteHoldCount()方法用于获取当前线程持有写锁的次数。
- 返回值是一个int类型的数值,表示当前线程持有写锁的次数。
- 使用场景:
- 可以通过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.获取当前线程持有读锁的次数
ReentrantReadWriteLock的
getReadHoldCount
()方法是用于获取当前线程持有读锁的次数的方法。
具体解释如下:
- 方法定义:
- int getReadHoldCount()
- 方法说明:
- getReadHoldCount()方法用于获取当前线程持有读锁的次数。
- 返回值是一个int类型的数值,表示当前线程持有读锁的次数。
- 使用场景:
- 可以通过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.获取正在等待写锁的线程列表
ReentrantReadWriteLock的
getQueuedWriterThreads
()方法用于获取正在等待写锁的线程列表。
具体解释如下:
- 方法定义:
- Collection
getQueuedWriterThreads
()
- Collection
- 方法说明:
- getQueuedWriterThreads()方法用于获取正在等待写锁的线程列表。
- 返回值是一个Collection类型,包含了正在等待写锁的线程。
- 使用场景:
- 可以通过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
方法返回一个包含正在等待获取读锁的线程的集合。
具体解释如下:
- 方法定义:
- Collection
getQueuedReaderThreads
()
- Collection
- 方法说明:
- 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
的特点如下:
- 乐观读锁:可以同时允许多个线程持有读锁,读取共享数据,即使有线程持有写锁,也不会阻塞读锁的读取。
- 悲观读锁(读锁):可以同时允许多个线程持有读锁,但是当有写锁被持有时,悲观读锁的获取会被阻塞等待。
- 写锁(排他锁):独占,只允许一个线程持有写锁,其他线程无法获取读锁或写锁。当持有写锁时,其他读线程和写线程被阻塞。
- 不可重入性:因为 StampedLock 的写锁是独占的,读锁是共享的。当一个线程持有写锁时,其他线程无法获取读锁,从而保证数据的一致性。同样地,当一个线程持有读锁时,其他线程无法获取写锁,以避免读取到正在被写入的数据。
- StampedLock 不具备公平锁特性,
StampedLock
的等待队列是无序的。 - 不支持条件变量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 位。写锁的版本号是递增的值的最高位取反的形式。写锁的版本号的变化只与写锁有关,与读锁的持有数量无关。
版本号的生成依赖于一个
volatile
的state
:
获取写锁
获取读锁
state变量
由于版本号的操作,在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)是否仍然有效。该方法的原理如下:
validate()
方法接受一个版本号(stamp)作为参数,用于验证该版本号是否仍然有效。版本号可以通过调用long readLock()
或long tryOptimisticRead()
方法获取。- 首先,
validate()
方法会将给定的版本号与当前的版本号进行比较。如果给定的版本号与当前版本号相等,表示该版本号仍然有效,可以继续操作。 - 如果给定的版本号与当前版本号不相等,说明在当前线程获取版本号后,可能有其他线程获取了写锁或者存在其他线程对锁进行了修改。在这种情况下,
validate()
方法会返回 false,表示给定的版本号已经失效。 - 当
validate()
方法返回 false 时,需要根据具体的业务逻辑来决定后续的处理方式。通常情况下,可以选择重新获取锁或者放弃当前操作。
通过调用 validate()
方法,可以判断给定的版本号是否仍然有效,从而确保在使用乐观读锁期间,没有其他线程对锁进行了修改。这一机制可以用于在使用乐观读锁时进行额外的校验,以确保数据的一致性和准确性。
2.等待队列
StampedLock
内部使用WNode
(Wait Node)来表示等待队列中的节点。WNode
是StampedLock
内部的一个内部类,用于管理线程在获取写锁时的等待和唤醒过程。
WNode 包含以下字段:
mode
:表示节点的模式,可以是独占模式(exclusive mode)或共享模式(shared mode)。next
:指向下一个节点的引用,用于构成等待队列。status
:表示节点的状态,可以是等待获取锁(WAITING)、已经获取锁(OWNED),或者是取消(CANCELLED)。thread
:表示节点对应的线程。exclusively
:一个标志位,表示节点是否以独占模式等待获取锁。
**WNode
的主要作用是构成 StampedLock
的等待队列。**当一个线程无法获取写锁时,会创建一个 WNode 节点并加入到等待队列中。当锁的状态发生改变时,会通过唤醒机制来通知等待队列中的节点。
**在 StampedLock
中,WNode
节点是以链表形式组织的,**通过 WNode
类的 next
字段来连接各个节点,形成等待队列。而在节点被唤醒时,会按照特定的规则从队列中移除该节点。在等待队列中,节点按照先进先出的顺序进行唤醒,即先加入队列的节点先被唤醒。当锁的状态发生改变时,会按照特定的规则从队列中移除节点,并唤醒下一个节点。
WNode
的模式可以是独占模式和共享模式。
独占模式
(exclusive mode): 当线程请求以独占模式获取锁(写锁)时,会创建一个以独占模式的 WNode 节点,并将其加入等待队列。在独占模式下,只有一个线程可以获得写锁,其他线程必须等待该线程释放写锁才能继续执行。共享模式
(shared mode): 当线程请求以共享模式获取锁(读锁)时,会创建一个以共享模式的 WNode 节点,并将其加入等待队列。在共享模式下,多个线程可以同时获得读锁,读锁之间不会互斥。但是当有线程持有写锁时,其他线程无法获取读锁,需要等待写锁释放。
WNode 的设计和使用是为了实现 StampedLock 的等待和唤醒机制,以及支持写锁的独占模式。通过合理管理和操作 WNode 节点,可以实现对写锁的竞争和等待的控制,确保线程安全和并发性能。
3.读写锁的创建
写锁的创建
StampedLock
的writeLock
方法,内部调用acquireWrite
方法获取写锁:
-
acquireWrite()
方法会先尝试直接获取写锁,即检查当前是否有其他线程持有读锁或写锁。如果没有,当前线程可以直接获取写锁并返回一个非零的stamp。这是一种乐观的锁获取方式,无需进行线程等待。 -
如果当前有其他线程持有读锁或写锁,会使用WNode队列来进行阻塞等待。此时,会创建一个以
独占模式
的WNode节点,并将当前线程加入等待队列。当前线程需要等待这些锁被释放才能获取写锁。
悲观读锁的创建
StampedLock
的readLock
方法,内部调用acquireRead()
方法用于获取读锁,其主要作用是阻塞当前线程,直到成功获取到读锁为止。
-
acquireRead
方法会尝试直接获取读锁,即检查当前是否有其他线程持有写锁。如果没有,当前线程可以直接获取读锁并返回一个非零的stamp。 -
如果当前有其他线程持有写锁,会使用WNode队列来进行阻塞等待。此时,会创建一个以**
共享模式
的WNode节点(多个线程可以同时获得读锁,读锁之间不会互斥),并将其加入等待队列。这样的场景通常发生在有其他线程持有写锁**时,当前线程需要等待写锁被释放才能获取读锁。
乐观读锁的创建
StampedLock
的tryOptimisticRead
方法
乐观读锁是一种非阻塞的锁获取方式,它通过读取**版本号
**(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类,可以实现更高效的读写操作,并且在多线程环境下提供更好的性能。