显式锁是自JDK1.5开始引入的排他锁。作为一种线程同步机制,其作用于内部锁相同。它提供了一些内部锁不具备的特性,但并不是内部锁的替代品。
显示锁(Explicit Lock)是java.util.concurrent.locks.Lock
接口的实例。该接口对显式锁进行了抽象,其定义的方法如图所示:
方法 | 描述 |
---|---|
void lock() | 获得锁 |
void lockInterruptibly() | 获取锁定,除非当前线程是 interrupted。 |
Condition newCondition() | 返回一个新Condition 绑定到该实例Lock 实例。 |
boolean tryLock() | 只有在调用时才可以获得锁。 |
boolean tryLock(long time, TimeUnit unit) | 如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。 |
void unlock() | 释放锁 |
类java.util.concurrent.ReentrantLock
是Lock接口的默认实现类
一个Lock接口实例就是一个显式锁对象,Lock接口定义的lock
方法和unlock
方法分别用于申请和释放相应Lock实例表示的锁。
显示锁的使用包括以下几个方面:
ReentrantLock
的实例作为显示锁使用try 代码
块为临界区【实例】
/**
* @Author: LiangYiFeng
* @Description 使用显式锁实现循环递增序列号生成器
* @Date: Create in 2022/8/17 11:22
* @Modified By:
*/
public class LockDemo implements Runnable{
private static int i=0;
private final Lock lock = new ReentrantLock(); // 创建一个Lock接口实例
public void run() {
lock.lock(); // 申请锁
try{
for (int j = 0; j < 10000; j++) {
i++;
}
} finally {
lock.unlock(); // 无论出现异常,finally总能执行解锁
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
Thread thread1 = new Thread(lockDemo);
Thread thread2 = new Thread(lockDemo);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);
}
}
输出结果:
20000
ReentrantLock即支持非公平锁也支持公平锁。ReentrantLock的一个构造器的签名如下:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
该构造器可以看出:我们在创建显式锁实例的时候可以指定相应的锁是否为公平锁(fair参数值为true,表示是公平锁)
公平锁保障锁调度的公平性往往是增加了线程的暂停和唤醒的可能性,即增加了上下文切换为代价的。因此,公平锁适合于锁被持有的时间相对长 或者 线程申请锁的平均间隔时间相对长的情形。
总的来说,使用公平锁的开销比使用非公平锁的开销要大,因此显示锁默认使用的是非公平调度策略。
分类 | 内部锁(synchronized) | 显式锁(Lock) |
---|---|---|
对象方面 | 基于代码块的锁,使用基础没有灵活性可言,要么使用,要么不使用 | 基于对象的锁,其使用可以充分发挥面向对象编程的灵活性。 |
代码方面 | 仅仅是一个关键字 | 是一个对象,可发挥面向对象编程的灵活性。 |
使用方面 | 简单易用,且不会导致锁泄露 | 容易被错用而导致锁泄露,必须注意将锁的释放操作放在finally块中 |
锁调度方面 | 仅支持非公平锁 | 即支持非公平锁,也支持公平锁 |
性能方面 | jdk 1.6/1.7 对内部锁做了一些优化,在特定情况下可以减少锁的开销。优化包括:锁消除、锁粗化、偏向锁和适配性锁 |
jdk1.6后,显式锁和内部锁之间的可伸缩性差异越来越小了。 |
内部锁的优点是简单易用——默认情况下选用内部锁,仅在需要显式锁所提供的特性的时候才选用显式锁。
显式锁的优点是功能强大——一般来说,新开发的代码中我们可以选用显式锁。
锁的排他性:使多个线程无法以线程安全的方式在同一时刻对共享变量进行读取(只是读取而不更新),这不利于提供系统的并发性。
对于同步在同一锁之上的线程而言:
读线程——对共享变量仅读取而没有更新的线程
写线程——对共享变量更新(包括先读取后更新)的线程
读写锁(Read/Write Lock) 是一种改进型的排他锁,也被称为共享/排他锁(Shared/ Exclusive)锁。
同时读取(只读)共享变量
,但是一次只允许一个线程对共享变量进行更新(包括读取后更新)读取
共享变量的时候,其他线程都无法更新
这些变量更新
共享变量的时候,任何线程都无法访问
该变量读写锁的功能是通过其——读锁(Read Lock)和写锁(Write Lock)实现的。
获得条件 | 排他性 | 作用 | |
---|---|---|---|
读锁 | 相应的写锁未被任何线程持有 | 对读线程是共享的,对写线程是排他的 | 允许多个读线程可以同时读取共享变量,并保障读线程读取共享变量期间没有其他任何线程能够更新这些共享变量 |
写锁 | 写锁未被其他任何线程持有,并且相应的读锁未被其他任何线程持有 | 对写线程和读线程都是排他的 | 使得写线程能够以独占的方式访问共享变量 |
首先明确一下,不是说 ReentrantLock 不好,只是 ReentrantLock 某些时候有局限。如果使用 ReentrantLock,可能本身是为了防止线程 A 在写数据、线程 B 在读数据造成的数据不一致,但这样,如果线程 C 在读数据、线程 D 也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。因为这个,才诞生了读写锁 ReadWriteLock。
ReadWriteLock 是一个读写锁接口
,读写锁是用来提升并发程序性能的锁分离技术,ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
而读写锁有以下三个重要的特性:
(1)公平选择性
:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入
:读锁和写锁都支持线程重进入。
(3)锁降级
:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
java.util.concurrent.locks.ReadWriteLock
接口 是对读写锁的抽象,其默认实现类是 java.util.concurrent.locks.ReentrantReadWriteLock
。ReadWriteLock 接口定义了两个方法
方法 | 描述 |
---|---|
Lock readLock() | 返回用于阅读的锁。 |
Lock writeLock() | 返回用于写入的锁。 |
实例:
public class ReadWriteLockDemo {
private static int sequence = 1;
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 读线程执行方法
public void reader(){
readLock.lock(); //申请读锁
try {
//读取共享变量
System.out.println(sequence);
} finally {
readLock.unlock();
}
}
// 读线程执行方法
public void writeLock(){
writeLock.lock(); //申请读锁
try {
//读取共享变量
sequence = 2;
System.out.println(sequence);
} finally {
writeLock.unlock();
}
}
}
读写锁适用于:
ReentrantReadWriteLock 所实现的读写锁是个可重入锁。ReentrantReadWriteLock支持锁的降级(Downgrade), 即一个线程持有读写锁的写锁的情况下可以继续获得相应的读锁。
ReentrantLock重入锁
,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。
在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1.
要想支持重入性,就要解决两个问题:
ReentrantLock支持两种锁:公平锁和非公平锁
。何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。