《 Java并发编程的艺术》之Java中的锁(第五章)

1 Lock接口
Lock和synchronized有何区别,区别在于synchronized是锁一个代码块或一个方法,需要先获取锁再释放锁,可操作性比不上Lock。而Lock的可操作性在于我们可以像下面这样操作:
------------------------------------------------------
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
lock1.lock();
lock2.lock();
lock1.unlock();
lock2.unlock();
------------------------------------------------------
分析:以上代码说明了Lock的可操作性,lock1拿到了锁,然后lock2获取到了锁后释放lock1锁,可以将锁一层嵌套一层,可操作性比synchronized强。
 
2 队列同步器
队列同步器AbstractQueuedSynchronizer,简称AQS
其作为java.util.concurrent包下不少类如(ReentrantLock,CountDownLactch等)的锁或同步组件的基础。内部维护了一个state状态+队列用于处理并发的请求。
 
同步器提供了模板方法,其模板方法分为如下三类:
  • 独占式获取和释放同步状态(state) tryAcquire()、tryRelease()
  • 共享式获取和释放同步状态(state) tryAcquireShared()tryReleaseShared()
  • 查询同步队列等待线程情况           isHeldExclusively()
可用于继承实现自己的同步组件,其实现代码如下:
public class Mutex implements Lock {
 
  // 静态内部类,自定义同步器
 private static class Sync extends AbstractQueuedSynchronizer {
  // 是否处于占用状态
  @Override
  protected boolean isHeldExclusively() {
   return getState() == 1;
  }
 
  // 当状态为0的时候获取锁
  @Override
  protected boolean tryAcquire(int acquires) {
   if (compareAndSetState(0, 1)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
   }
   return false;
  }
 
  // 释放锁,将状态设置为0
  @Override
  protected boolean tryRelease(int releases) {
   if(getState() == 0) throw new IllegalMonitorStateException();
   setExclusiveOwnerThread(null);
   setState(0);
   return true;
  }
 
  // 返回一个Condition,每个condition都包含了一个condition队列
  Condition newCondition() { return new ConditionObject(); }
 }
 // 仅需要将操作代理到Sync上即可
 private final Sync sync = new Sync();
 @Override
 public void lock() { sync.acquire(1); }
 @Override
 public boolean tryLock() { return sync.tryAcquire(1); }
 @Override
 public void unlock() { sync.release(1); }
 @Override
 public Condition newCondition() { return sync.newCondition(); }
 public boolean isLocked() { return sync.isHeldExclusively(); }
 public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
 @Override
 public void lockInterruptibly() throws InterruptedException {
  sync.acquireInterruptibly(1);
 }
 @Override
 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }
}
分析:首先使用一个类实现了Lock接口,然后在内部实现一个Sync(继承AQS)同步组件。最后通过调用同步组件的方法来达到同步的目的。
 
3 可重入锁
支持重新进入的锁,该锁支持一个线程对资源的重复加锁。其中synchronized和ReentrantLock都支持可重入锁。
另外,锁有一个公平性,等待越久的线程约会先获取锁(顺序FIFO获取),避免"饥饿"现场(ReentrantLock可控制是否公平锁)。
 
实现重进入
什么是重进入?
任意线程获取锁之后,能够再次获取锁而不会被阻塞
 
// 以下是详细代码===============================
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();// 1 如果非获取到锁的线程,获取状态
    if (c == 0) {  // 如果状态为空则释放了所有,当前新的线程可索取锁
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {  
        // 2 如果当前线程是那个拿到锁的线程,就直接进入,并且给他的状态值自增
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
// 上面因为当前拥有锁的再次进入会增加state值大小,所以需要在释放的时候进行处理
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  //1 state减去释放的值
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { //如果当前state=0,那就是真的释放操作,并且使用setExclusiveOwnerThread释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
 
4 读写锁(不讲解,感兴趣可自行学习)
Java中读写锁一般指的是 ReentrantReadWriteLock,可将读写分离,可多次重复读(共享),但是写的时候是互斥(排他)的。
ReentrantReadWriteLock自定义了同步器,通过同步器状态维护了多个读线程 + 一个写线程。
 
5 LockSupport工具
方法如下:
 

 
使用例子如下:
       //获取当前线程
        final Thread currentThread = Thread.currentThread();
     
        //在park之前先进行一次unpark        
        LockSupport.unpark(currentThread);
        
        System.out.println("开始阻塞!");
        // 由于在park之前进行了一次unpark,所以会抵消本次的park操作。因而不会阻塞在此处        
        LockSupport.park(currentThread);
        System.out.println("结束阻塞!");
 
参考文章:
 
6 Condition接口
当前接口用于 通知/等待,但是操作前均需要获取锁,是基于lock接口来实例化的。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
    lock.lock();  //使用前先获取锁
    try {
        condition.await();  //释放锁并进入阻塞状态
    } finally {
        lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal();  //通知获取锁
    } finally {
        lock.unlock();
    }    
}
 
实现分析(以下是ConditionObject源码,分析Condition时均以这个说明,其入口在ReentrantLock.newCondition()):
// 1 调用await时
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
     // await时,当前线程会先进入等待队列
    Node node = addConditionWaiter();
     // 然后释放同步状态,也就是释放对应的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
     // 然后唤醒同步队列后继节点
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
 
// 2 调用signal唤醒
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter // 因为await时会加新的线程加入等待队列尾节点,所以首节点是加入最久的,这时候唤醒的话会取首节点(等最久的)去唤醒
    if (first != null)
        doSignalAll(first);
}
// 最后`signal`还是调用了以下方法来进行唤醒,LockSupport.unpark(thread);
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
 
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章