- 显示锁
只要实现了Lock接口的锁都叫显示锁。锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。当然,这种方式简化了同步的管理,可是扩展性没有显示的锁获取和释放来的好。例如,针对一个场景,手把手进行锁获取和释放,先获得锁A,然后再获取锁B,当锁B获得后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D,以此类推。这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却容易许多。
- Lock接口详解
Lock接口及继承关系类
Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。其中: Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
- synchronized与Lock的区别
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 代码层面,是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
什么是可重入性?
什么是公平锁(安全锁)和非公平锁(非安全锁)?
- 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
- 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列 是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。如果两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
- Lock接口源码如下:
public interface Lock {
/**
* Acquires the lock.
*/
void lock();
/**
* Acquires the lock unless the current thread is
* {@linkplain Thread#interrupt interrupted}.
*/
void lockInterruptibly() throws InterruptedException;
/**
* Acquires the lock only if it is free at the time of invocation.
*/
boolean tryLock();
/**
* Acquires the lock if it is free within the given waiting time and the
* current thread has not been {@linkplain Thread#interrupt interrupted}.
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* Releases the lock.
*/
void unlock();
}
Lock类可以创建Condition对象,Condition对象是用来线程等待和线程唤醒的,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair //传入参数设置是否是公平锁) {
sync = fair ? new FairSync() : new NonfairSync();
}
//公平锁的函数原型
/*** Sync object for fair locks */
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/** * Fair version of tryAcquire. Don't grant access unless * recursive call or no
waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 和非公平锁相比,这里多了一个判断:是否有线程在等待
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); return true;
}
}else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
//非公平锁函数原型
/*** Sync object for non-fair locks */
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/*** Performs lock. Try immediate barge, backing up to normal * acquire on failure.
*/
final void lock() { // 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
elseacquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/*** Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
sync是ReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS。FailSync和NonFailSync亦为Reentrantlock的静态内部类,都继承于Sync。
ReentrantLock的应用:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private Lock lock = new ReentrantLock();
Integer count = 0;
public void add(){
lock.lock();
try{
count++;
}finally {
lock.unlock();
}
}
public int getCount() {
return count;
}//线程
private static class CountAdd extends Thread{
private ReentrantLockDemo simp;
public CountAdd(ReentrantLockDemo simp) {
this.simp = simp;
}
@Override
public void run() {
for(int i=0;i<10000;i++){
simp.add();
}
}
}
public static void main(String[] args) throws InterruptedException{
ReentrantLockDemo demo = new ReentrantLockDemo();
CountAdd count1 = new CountAdd(demo);
CountAdd count2 = new CountAdd(demo);
CountAdd count3 = new CountAdd(demo);
CountAdd count4 = new CountAdd(demo);
count1.start();
count2.start();
count3.start();
count4.start();
Thread.currentThread().sleep(100);
System.out.println("count进行多线程相加后的结果:"+demo.getCount());
}
}
- ReentrantReadWriteLock锁(可重入读写锁)
- 没有其他线程的写锁;
- 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个;
- 没有其他线程的读锁
- 没有其他线程的写锁
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。