Java 锁
synchronized 关键字实现锁:
在 Java 中 synchronized
关键字将会隐式的获取锁, 但是它将锁的获取和释放固化了, 也就是先获取在释放. synchronized
关键字经过编译后会在同步代码块的前后分别形成 monitorenter
和 monitorexit
两个字节码指令, 这两个字节码指令都需要一个 reference
类型的参数来指明要锁定和解锁的对象.
在虚拟机执行 monitorenter
指令时, 首先要尝试获取对象的锁. 如果这个对象没有被锁定, 或者当前对象已经拥有了哪个对象的锁, 把锁的计数器加 1, 相应的, 在执行 monitorexit
指定时会将锁计数器减 1, 当计数器为 0 时, 锁就被释放. 如果获取对象锁失败, 那当前线程就要阻塞等待, 直到对象锁被另外一个线程释放为止.
synchronized
同步块对同一条线程来说是可重入的, 不会出现把自己锁死的情况; 同步块在已进入的线程执行完之前, 会阻塞后面其它线程的进入.
lock(), lockInterruptlly() 的区别
public final void acquireInterruptibly(int arg) throws InterruptedException {
// 这里会响应中断信号
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
队列同步器实现锁:
队列同步器使用一个 int
类型的成员变量表示同步状态. 实现原理是依赖硬件的原子性 CAS 指令来完成对状态的更新, 成功更新该状态即可表示对锁的获取(太尼玛机智了), 对于没能获取到锁的线程则将它们阻塞掉, 使它们无法被调度器调度. 当占有线程的锁释放锁之后, 将阻塞的线程唤醒, 又让它们去更改状态.
-
独占锁
-
自定义独占锁: 只能有一个线程获取到锁, 并且不可重入
class Mutext implements Lock { private final Sync sync = new Sync(); public void lock() { sync.acquire(1); } public boolean tryLock() { return sync.tryAcquire(1); } public void unlock() { sync.release(1); } publc Condition newCondition() { return sync.newCondition(); } public boolean isLocked() { return sync.isHeldExclusively(); } public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } public void lockInterruptibly throws InterruptedException { sync.acquireInterruptibly(1); } public boolean tryLock(long timeOut, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } private static class Sync extends AbstractQueuedSynchronizer { // 锁是否处于占用状态 protected boolean isHeldExclusively() { return getState() == 1; } public boolean tryAcquire(int acquires) { /* * 这里通过底层的原子性 CAS 指令来更新状态, 一旦状态更新成功, * 则将当前线程设置为占有锁的线程 */ if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int releases) { if (getState() == 0) { throw new IllegalMonitorStateException(); } setExclusiveOwnerThread(null); // 这里不要调用 compareAndSetState() 方法, // 因为永远只能有一个线程获取到锁, 只会有一个线程调用该方法, // 不存在多线程竞争的关系 setState(0); return true; } Condition newCondition() { return new ConditionObject(); } } }
-
实现原理:
-
获取锁:
public final void acquire(int arg) { // 这里调用的 tryAcquire() 方法就是我们子类所重写的方法 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } private Node addWaiter(Node mode) { // 以当前线程构建节点 Node node = new Node(Thread.currentThread(), mode); // 这里快速尝试一下能不能在尾部添加节点 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 若快速添加节点失败了, 则调用 enq() 方法将节点添加到队列尾部 enq(node); return node; } private Node enq(final Node node) { /* * 这里通过死循环将节点添加到队列尾部, 该方法保证了节点一定会添加成功 * TODO 如果在该循环添加还未成功时, 占有锁的线程已经释放了锁怎么办 */ for (;;) { Node t = tail; if (t == null) { // 这里会创建一个空的节点 Node h = new Node(); h.next = node; node.prev = h; if (compareAndSetHead(h)) { tail = node; return h; } } else { // 建立前向的指向关系 node.prev = t; // 利用底层的原子性 CAS 指令设置尾节点 if (compareAndSetTail(t, node)) { // 尾节点设置成功后再建立后向的指向关系 t.next = node; return t; } } } } final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 当前节点的前一个节点是头节点则尝试获取锁 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; return interrupted; } // 未能获取到锁 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { // 前驱节点被取消或中断, 将其从等待队列中移除 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 将当前节点的前一个节点的状态设置为 Node.SIGNAL * TODO 不明白为什么这里需要使用 compareAndSetWaitStatus() * 按理说不会出现多线程设置的情况 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { // 将当前线程阻塞, 也就是将未能获取到锁的线程阻塞 LockSupport.park(this); return Thread.interrupted(); }
-
释放锁:
public final boolean release(int arg) { // 这里调用自定的 tryRelease() 方法 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) { // 释放锁 unparkSuccessor(h); } return true; } return false; } private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) // 将头节点状态设置为初始状态 compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // 唤醒头节点的下一个节点的线程 LockSupport.unpark(s.thread); }
-
-
-
共享锁: 共享锁允许多个线程获取锁
-
自定义共享锁:
public class TwinsLock implements Lock { private final Sync sync = new Sync(2); public void lock() { sync.accquireShared(1); } public void unlock() { sync.releaseShared(1); } private static final class Sync extends AbstractQueuedSynchronizer { Sync(int count) { if (count <= 0) { throw new IllegalArgumentException("count must be larger than zero"); } setState(count); } // 获取共享锁 public int tryAcquireShared(int reduceCount) { for (;;) { int current = getState(); int newCount = current - reduceCount; if (newCount < 0 || compareAndSetState(current, newCount)) { return newCount; } } } // 释放共享锁 public boolean tryReleaseShared(int returnCount) { for (;;) { int current = getState(); int newCount = current + returnCount; /* * 这里需要使用底层的原子性 CAS 指令, 因为可能同时 * 存在多个线程释放锁 */ if (compareAndSetState(current, newCount)) { return true; } } } } }
-
-
排它重入锁: 该锁能够支持一个线程对资源的重复加锁. 先对锁进行获取的请求一定先被满足, 则锁是公平的; 反之, 是不公平的.
ReentrantLock
是 Java 提供的可重入锁.-
非公平获取锁:
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) { throw new Error("Maxium 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) { // 是等待队列中的第一个线程才能获取锁 if (isFirst(current) && 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; }
-
释放锁:
proteced final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } boolean free = false; // 当所有锁释放完成之后才会返回 true if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
-
-
读写锁:
读写锁维护一个读锁和一个写锁. 读锁是一个支持重进入的共享锁, 写锁是一个支持重进入的排它锁. Java 提供了
ReentrantReadWriteLock
.-
使用示例:
public class Cache { static Map<String, Object> map = new HashMap<>(); static ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); static Lock rLock = rwlock.readLock(); static Lock wLock = rwlock.writeLock(); public static final Object get(String key) { rLock.lock(); try { return map.get(key); } finally { rLock.unlock(); } } public static final Object put(String key, Object value) { wLock.lock(); try { return map.put(key, value); } finally { wLock.unlock(); } } public static final void clear() { wLock.lock(); try { map.clear(); } finally { wLock.unlock(); } } }
-
实现原理:
将状态变量进行分割, 高 16 位表示读锁状态, 低 16 位表示写锁状态.
-
写锁的获取: 若当前线程已经获取了写锁, 则增加写状态; 若读锁已经被获取或者该线程不是已经获取写锁的线程, 则进入等待状态.
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); // 通过位运算获取写锁状态 int w = exclusiveCount(c); if (c != 0) { // 存在读锁或者当前线程不是已经获取写锁的线程 if (w == 0 || current != getExclusiveOwnerThread()) { return false; } if (w + exclusiveCount(acquires) > MAX_COUNT) { throw new Error("Maximum lock count exceeded"); } // 当前线程是已经获取写锁的线程, 重入 setState(c + acquires); return true; } // 第一次竞争获取写锁 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) { return false; } setExclusivgeOwnerThread(current); return true; }
-
读锁的获取:
protected final int tryAcquireShared(int unused) { for(;;) { int c = getState(); int nextc = c + (1 << 16); if (nextc < c) { throw new Error("Maximum lock count exceeded"); } if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) { return -1; } // 当前线程获取了写锁或者写锁未被获取, 则成功获取读锁 if (compareAndSetState(cm nextc)) { return 1; } } }
-
-
-
锁降级: 把持住当前拥有的写锁, 在获取到读锁, 随后释放写锁的过程
public void processData() { readLock.lock(); if (!update) { readLock.unlock(); // 获取写锁 writeLock.lock(); try { if (!update) { // 准备数据 update = true; } readLock.lock(); } finally { writeLock.unlock(); } // 写锁降级为读锁 } try { // 使用数据 } finally { readLock.unlock(); } }
-
Condition 实现原理
- 等待队列: 线程调用
Condition#await()
方法导致该线程释放锁, 进入等待队列(从队列角度看是同步队列的头结点进入了等待队列中). - 并发包中的 Lock 拥有一个同步队列和多个等待队列(因为 Condition 可能有多个)
- 调用
Condition#signal() 方法
将会唤醒在等待队列中等待时间最长的结点, 在唤醒结点之前, 会将结点移动到同步队列中. - 从
await()
方法中醒来的线程已经成功获取了锁.
- 等待队列: 线程调用
-
阻塞队列实现原理
- ArrayBlockingQueue: 由数组结构组成的有界阻塞队列
- LinkedBlockingQueue: 由链表结构组成的有界阻塞队列
- PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列
- DelayQueue: 支持延时获取元素的无界阻塞队列, 队列中的元素必须实现 Delayed 接口, 在创建元素时可以指定多久才能从当前队列获取元素.
- 缓存系统的设计: 可以用 DelayQueue 保存缓存元素, 使用线程循环查询 DelayQueue, 一旦能获取元素时, 表示缓存有效期到了.
- 定时任务调度: 使用 DelayQueue 保存当天将会执行的任务和执行时间, 一旦从 DelayQueue 中获取到任务就开始执行.
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) // 队列满等待 notFull.await(); enqueue(e); } finally { lock.unlock(); } } private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; // 存放了数据, 通知等待的 take() 方法可以取数据了 notEmpty.signal(); } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) /// 队列空等待 notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); // 取走了元素, 通知等待的 put() 方法可以存放数据了 notFull.signal(); return x; }
-
Fork/Join 实现原理
任务分割出的子任务会添加到当前工作线程所维护的双端队列中, 进入队列的头部. 当一个工作线程的队列暂时没有任务时, 它会随机从其它工作线程的队列的尾部获取一个任务.
-
ForkJoin 关键是在于它需要实现
compute()
方法, 在该方法中需要对任务进行判断, 如果任务足够小则直接执行; 反之则对任务进行拆分. -
ForkJoinWorkerThread: 内部有所属的 ForkJoinPool 和 ForkJoinPool.WorkQueue; 在构造方法中将自己注册到 ForkJoinPool 中
this.pool = pool; this.workQueue = pool.registerWorker(this); // 返回一个工作队列, 属于该线程 WorkQueue w = new WorkQueue(this, wt); // 将该工作队列保存到 ForkJoinPool 的队列数组里面 ws[i] = w;
-
ForkJoinTask#fork()
Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) // 后续调用 ((ForkJoinWorkerThread)t).workQueue.push(this); else // 第一次调用 ForkJoinPool.common.externalPush(this); return this;
-