一、Lock接口的认识与使用
1、Lock、ReentrantLock(公平锁)、ReadWriteLock(读写锁)、
AQS类(位置) -- jdk1.8/rt.jar/com/java/util/concurrent/atomic/locks
2、Lock 与synchronized 的比较
Lock 需要显示地获取和释放锁,操作繁琐 但能让代码更灵活。
synchronized 不需要显示地获取和释放锁,操作简单
3、Lock的优点
使用Lock可以方便的实现公平性(ReentrantLock);
非阻塞的获取锁;
能被中断的获取锁;
超时获取锁;
4、自实现 Lock
public class MyLock implements Lock {
private boolean isLocked = false; // 是否加锁标志
private Thread lockBy = null; // 获取锁的线程
private int lockCount = 0; // 当前的线程 获取的锁次数
@Override
public synchronized void lock() {
Thread currentThread = Thread.currentThread(); // Thread-0 获取进来的线程
while (isLocked && currentThread != lockBy) // 判断是否加锁并且不为同一个线程,(若为同一个线程,则不进行wait(),可以再次获得锁)。实现了锁重入
try {
wait(); // 将当前线程停止
} catch (InterruptedException e) {
e.printStackTrace();
}
isLocked = true;
lockBy = currentThread; // 将 线程判断标志 赋值为第一个获取锁的线程,记录获取锁的线程
lockCount ++; // 记录该线程获取锁的次数
}
@Override
public synchronized void unlock() {
if(lockBy == Thread.currentThread()) { // 当是同一个线程时才能 释放锁
lockCount --; // 线程每调用一次 unlock() 则 -1 该线程获取锁的次数
if(lockCount == 0) { // 当获取锁的线程 将所有的锁都释放之后,才能将Lock锁线程 唤醒
notify(); // 唤醒当前 synchronized 的锁的其他线程
isLocked = false; // 将锁释放
}
}
}
}
二、AbstractQueuedSynchronizer(AQS)详解
为实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等等)提供一个框架。此类的设计目标是成为依靠单个原子 int值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()\setState(int)\compareAndSetState(int,int) 方法来操作以原子方式更新的int值;
此类支持默认的独占 模式和共享 模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock 中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。
AQS实现类 lock 与 unlock 方法 的解析
1、使用AQS实现一个简单的锁
public class MyLock2 implements Lock {
private Helper helper = new Helper();
/**
* jdk文档:
* 应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。
* 类 AbstractQueuedSynchronizer 没有实现任何同步接口。
* 而是定义了诸如 acquireInterruptibly(int) 之类的一些方法,
* 在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。
*/
private class Helper extends AbstractQueuedSynchronizer {
// 获取锁的方法
@Override
protected boolean tryAcquire(int arg) {
// 如果第一个线程进来,可以拿到锁,因此我们可以返回true
// 如果第二个线程进来,则拿不到锁,返回false。有种特例,如果当前进来的线程和当前保存的线程是同一个线程,则可以拿到锁,但是有代价,要更新状态值
// 如何判断是第一个线程进来还是其他线程进来?
int state = getState();
Thread t = Thread.currentThread();
if (state == 0) {
if (compareAndSetState(0, arg)) { // 没有被获取锁
setExclusiveOwnerThread(t); // 设置为独占锁
return true;
}
} else if (getExclusiveOwnerThread() == t) { // 同一线程进入,重入
setState(state + 1); // 获取锁次数 +1
return true;
}
return false;
}
// 释放锁方法
@Override
protected boolean tryRelease(int arg) {
// 锁的获取和释放肯定是一一对应的,那么调用此方法的线程一定是当前线程
if (Thread.currentThread() != getExclusiveOwnerThread()) { // 若不是获取锁的线程,则抛出异常
throw new RuntimeException();
}
int state = getState() - arg; // 将该线程获取锁的次数 -1
boolean flag = false
// 若state == 0,说明该线程获取锁的次数已为0
if (state == 0) {
setExclusiveOwnerThread(null);
flag = true;
}
setState(state);
return flag;
}
Condition newCondition() {
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
helper.release(1);
}
@Override
public Condition newCondition() {
return helper.newCondition();
}
}
三、公平锁、 读写锁、锁降级与升级
1、AQS下的ReentrantLock的公平锁
- 公平是针对锁的获取而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序。
- 原理:将每一个进来获取锁的线程都加入进链表中,在获取锁的时候判断同步队列是否有前驱节点,若有,则获取失败;
2、读写锁
排他锁(写锁) 与 共享锁(读锁)
排他锁:同一时间内只允许一个线程访问
共享锁:同一时间内允许多个读操作线程访问
以map为例的读写锁demo
public class Demo {
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock(); // 新建 ReentrantReadWriteLock 读写锁对象
private Lock r = rwl.readLock(); // 获取读锁
private Lock w = rwl.writeLock(); // 获取写锁
public Object get(String key) {
r.lock(); // 加读锁(共享),允许多个线程进来获取锁
System.out.println(Thread.currentThread().getName() + " 读操作在执行..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
} finally {
r.unlock();
System.out.println(Thread.currentThread().getName() + " 读操执行完毕..");
}
}
public void put(String key, Object value) {
w.lock(); // 加写锁(排他),不允许多个线程进入获取锁
System.out.println(Thread.currentThread().getName() + " 写操作在执行..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
} finally {
w.unlock();
System.out.println(Thread.currentThread().getName() + " 写操作执行完毕..");
}
}
}
3、锁降级与升级
1、锁降级是指写锁降级为读锁
在写锁没有释放的时候,获取到读锁,再释放写锁。
2、锁升级:把读锁升级为写锁
在读锁没有释放的时候,获取到写锁,再释放读锁。
有啥不正确的还望大佬指正,谢谢