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

目录

AQS

获取锁失败,入队,加入到同步队列尾部

头节点获取锁成功,释放锁时,头节点将出队

 

 

 

 

重入锁ReentrantLock

重入锁的特性

  • 可重入

  • 公平性获取锁

1.可重入实现原理

同步状态(可认为就是锁) state 表示锁被一个线程重复获取的次数

  • state == 0 表示当前锁未被线程持有

  • state !=0 计数器,表示锁被某个线程重进入的次数

1. 锁的获取

如果尝试获取锁的线程为当前持有锁的线程,可再次获取成功,并将计数器加1。

final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  //如果当前尝试获取锁的线程为持有锁的线程,可再次获取成功,并将计数器加1。
  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;
}
 

2. 锁的释放

protected final boolean tryRelease(int releases) {
  //释放锁时,将计数器减1.
  int c = getState() - releases;
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
  boolean free = false;
  //如果计数器的值变为0,将持有锁的线程设为null,表示锁未被持有
  if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
  }
  setState(c);
  return free;
}

 

2.公平锁与非公平锁实现原理

非公平锁

final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  //如果当前尝试获取锁的线程为持有锁的线程,可再次获取成功,并将计数器加1。
  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;
}
 

公平锁

protected final boolean tryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    //与非公平锁的不同,多了!hasQueuedPredecessors() 判断条件,会首先判断同步队列中是否等待的线程
    //公平锁:同队队列中有等待的线程,获取失败,插入同步队列尾部
    //非公平锁:直接尝试获取锁,获取失败,插入同步队列尾部
    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;
}
 

 

读写锁ReadWriteLock

什么是读写锁

  • 读锁:共享锁,可以多个线程同时持有。约束条件:当读锁被持有,后续线程只能获取读锁。

  • 写锁:排他锁,同一时刻只能同一个线程持有。约束条件:当写锁被持有,后续获取读锁或写锁的线程都将被阻塞。

1.读写状态的设计

将同步状态state“按位切割使用”。

高16位为读状态,表示读锁被获取的次数;低16位为写状态,表示写锁被获取的次数。

 

 

读写锁支持重进入,以读写线程为例:读线程获取读锁之后,能够再次获取读锁;写线程获取写锁后,能够再次获取写锁,也能够获取读锁。

以上状态表示某线程获取了写锁3次,获取了读锁2次。

2.写锁的获取与释放

2.1写锁的获取

每次写锁获取成功时将写状态加1。

据读写锁的约束条件,获取写锁时有如下限制:

1)如果读锁被持有了,无法获取写锁

2)如果读锁没有没持有,但是写锁的持有线程不是当前线程,无法获取写锁

 protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            //获取写锁的获取次数
            int w = exclusiveCount(c);
            /**
            * c!=0,说明读锁或写锁被占有
            */
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                /**
                 * 存在读锁或者当前线程不是已经获取写锁的线程,获取写锁失败
                 */
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            /**
             * c==0,尝试获取写锁,获取成功后将写状态+1
             */
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

 

2.2写锁的释放

每次写锁释放成功时将写状态减1,当写状态为0时,表示写锁被释放,从而等待的读写线程能够继续访问读写锁。

3.读锁的获取与释放

3.1读锁的获取

每次读锁获取成功时将写状态加1。

据读写锁的约束条件,获取读锁时有如下限制:

如果其他线程获取了写锁,则当前线程获取读锁失败。

(说明:读状态是所有线程获取读锁的次数总和,每个线程各自获取读锁的次数保存在ThreadLocal中,由线程自己维护。以下代码做了精简,仅保留了必要的部分)

protected final int tryAcquiredShared(int unused) {
        for (;;) {
            int c = getState();
            int nextc = c + (1 << 16);
            if (nextc < c) {
                throw new Error("Maxinum lock count exceeded");
            }
            /**
            * 如果其他线程获取了写锁,当前线程获取写锁失败。
            */
            if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) {
                return -1;
            }
            if (compareAndSetState(c, nextc)) {
                return 1;
            }
        }
    }
 

3.2读锁的释放

每次读锁释放成功时将读状态减1。

Condition实现原理

什么是Condition

Condition接口提供了await()、signal()、signalAll()等方法,配合Lock可以实现等待/通知模式。比如用于实现生产者/消费者。

1.等待队列

每个Condition对象都维持着一个等待队列,维持着当前等待在当前condition对象上的线程队列。

一个Lock可对应多个Condition对象,每个condition对象维持一个等待队列。

2.等待(await()方法)

调用await方法(或以await开头的方法),线程将释放锁,加入到等待队列的尾部。

从同步队列和等待队列的角度来看,调用await()方法时将释放锁,导致节点从同步队列移除,然后构造节点加入到等待队列找中。

整个过程相当于同步队列的首节点(获取了锁的节点)移动到等待队列尾部。

 

 

3.通知(signal()、signalAll()方法)

调用signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点)。

从同步队列和等待队列的角度来看,被唤醒的线程将加入到同步队列中,一旦线程获取锁成功,将从await()方法返回。

调用signal()方法,相当于对等待队列中的每一个线程执行了signal()方法,相当于将等待队列中的所有节点都移动到同步队列中。

(顺序问题:如果为非公平模式,所有从等待队列中唤醒的线程都将先尝试获取锁,如果获取失败才加入到同步队列中)

 

posted @ 2020-03-27 13:53  Ye_yang  阅读(427)  评论(0编辑  收藏  举报