Lock和 ReentrantLock
Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throw InterruptedException;
void unlock();
Condition newCondition();
}
一、ReentrantLock
1.1 特点
- ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性的保证。
- 获得ReentrantLock的锁与进入synchronized块有着相同的内存语义,释放ReentrantLock锁与退出synchronized块有相同的内存语义。
- ReentrantLock提供了与synchronized一样的可重入加锁的语义。
- ReentrantLock支持Lock接口定义的所有获取锁的模式。
- 与synchronized相比,ReentrantLock为处理不可用的锁提供了更多灵活性。
1.2 必须在finallly释放锁
Lock lock = new ReentrantLock();
...
lock.lock( );
try {
/ / 更新对象的状态
// 捕获异常,必要时恢复到原来的不变约束}
finally {
lock.unlock();
}
1.3 可轮询的和可定时的锁请求
可定时的与可轮询的锁获取模式,是由tryLock方法实现,与无条件的锁获取相比,它具有更完善的错误恢复机制。可以规避死锁发生.
1.3.1 轮询锁
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
} finally {
toAcct.lock.unlock();
}
}
} finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
1.3.2 定时锁
public boolean trySendOnSharedLine(String message,
long timeout, TimeUnit unit)
throws InterruptedException {
long nanosToLock = unit.toNanos(timeout)
- estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, NANOSECONDS))
return false;
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
1.4 可中断的锁获取操作
- Lock.lockInterruptibly():该锁与lock相似,但可以被中断
- 如果线程未被中断,也不能获取到锁,就会一直阻塞下去,直到获取到锁或发生中断请求
- 定时的lock.tryLock(timeout, unit)同样能响应中断.可定时,可中断时使用tryLock
1.5 非块结构加锁
- 内置锁是基于块结构的加锁
- Lock可以使块与块交叉实现非块结构的加锁(连锁式加锁或者锁耦合),例:链表中,next节点加锁后,释放pre节点的锁.也称为连锁式加锁或锁连接.
1.6 公平性
非公平锁 性能一般优于公平锁,当发生加锁的时候,公平会因为挂起和重新开始线程的代价带来巨大的性能开销。
1.6.1 公平锁
——Lock fairLock = new ReentrantLock(true);
- 在公平的锁上,线程将按照它们发出请求的顺序来获得锁
- 在非公平的锁上,则允许”插队“:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁
- 公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能(非公平性的锁允许线程在其他线程的恢复阶段进入加锁代码块)
- 当持有锁的时间相对较长,或者请求锁的平局时间间隔较长,那么应该使用公平锁
- 内置锁默认为非公平锁
1.7 在Synchronized和ReentrantLock之间作出选择
在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized
Synchronized是JVM的内置属性,能执行一些优化,并且基于块结构与特定栈管理,便于检测识别发生死锁。
Java6提供了一个管理和调试接口,锁可以通过该接口进行注册,从而与ReentrantLocks相关的加锁信息就能出现在转储中,并通过其他的管理接口和调试接口来访问
二、 读—写ReadWriteLock锁
1. ReadWriteLock
对于在多处理器系统上被频繁读取的数据结构,读 - 写锁能够提高性能。而在其他情况下,读 - 写锁的性能比独占锁的性能要略差一些,这是因为它们的复杂性更高
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
2. 读写锁的可选实现:
- 释放优先。写入锁释放后,应该优先选择读线程,写线程,还是最先发出请求的线程
- **读线程插队。**锁由读线程持有,写线程再等待,再来一个读线程,是继续让读线程访问,还是让写线程访问
- **重入性。**读取锁和写入锁是否可重入
- **降级。**将写入锁降级为读取锁
- **升级。**将读取锁升级为写入锁
在非公平的锁中,线程获得访问许可的顺序是不确定的。写线程降级为读线程是可以的,当从读线程升级为写线程这是不可以的(这样会导致死锁)
3. ReentrantReadwriteLock
当锁被持有的时间相对较长,并且大部分操作都不会改变锁守护的资源,那么读-写锁能够改进并发性。ReadwriteMap使用了ReentrantReadwriteLock来包装 Map,使得它能够在多线程间被安全地共享,并仍然能够避免"读-写"或者"写-写"冲突’。现实中,concurrentHashMap的性能已经足够好了,所以可以使用它.
package com.study.framework.annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Description: 自定义,线程安全的 map ,用读写锁改造HashMap
*/
public class SafeMap {
//非线程安全的map
private static Map<String, Object> map = new HashMap<>();
//可重入读写锁
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
private static Lock r = lock.readLock();
//写锁
private static Lock w = lock.writeLock();
//获取一个key 对应的值
public static final Object get(String key) {
//加 读锁
r.lock();
Object obj = null;
try {
obj = map.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
r.unlock();
}
return obj;
}
//设置 key 对应的value
public static final Object put(String key, Object value) {
//加 写锁
w.lock();
Object obj = null;
try {
obj = map.put(key, value);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
w.unlock();
}
return obj;
}
}