【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)

最后

针对以上面试题,小编已经把面试题+答案整理好了

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

面试专题

image

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

image

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

while (isFull())

wait();

boolean wasEmpty = isEmpty();

doPut(v);

if (wasEmpty)

notifyAll();

}

使用notify容易丢失信号,所以大多数情况下用notifyAll,比如take notify,却通知了另外一个take,没有通知put,那么这就是信号丢失,是一种“被劫持的”信号。

因此只有满足下面两个条件,才能用notify,而不是notifyAll:

  • 所有等待线程的类型都相同

  • 单进单出

2.5 示例:阀门类A Gate Class


和第5章的那个TestHarness中使用CountDownLatch类似,完全可以使用wait/notifyAll做阀门。

@ThreadSafe

public class ThreadGate {

// CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n)

@GuardedBy(“this”) private boolean isOpen;

@GuardedBy(“this”) private int generation;

public synchronized void close() {

isOpen = false;

}

public synchronized void open() {

++generation;

isOpen = true;

notifyAll();

}

// BLOCKS-UNTIL: opened-since(generation on entry)

public synchronized void await() throws InterruptedException {

int arrivalGeneration = generation;

while (!isOpen && arrivalGeneration == generation)

wait();

}

}

3 Explicit Condition Objects

===========================================================================================

Lock是一个内置锁的替代,而Condition也是一种广义的内置条件队列

Condition的API:

public interface Condition {

void await() throws InterruptedException;

boolean await(long time, TimeUnit unit)throws InterruptedException;

long awaitNanos(long nanosTimeout) throws InterruptedException;

void awaitUninterruptibly();

boolean awaitUntil(Date deadline) throws InterruptedException;

void signal();

void signalAll();

}

内置条件队列存在一些缺陷,每个内置锁都只能有一个相关联的条件队列。所以在BoundedBuffer这种类中,多个线程可能在同一个条件队列上等待不同的条件谓词,所以notifyAll经常通知不是同一个类型的需求。如果想编写一个带有多个条件谓词的并发对象,或想获得除了条件队列可见性之外的更多的控制权,可以使用Lock和Condition,而非内置锁和条件队列,这更加灵活。

一个Condition和一个lock关联,想象一个条件队列和内置锁关联一样。在Lock上调用newCondition就可以新建无数个条件谓词,这些condition是可中断的、可有时间限制的,公平的或者非公平的队列操作。

The equivalents of wait, notify, and notifyAll for Condition objects are await, signal, and signalAll。

下面的例子就是改造后的BoundedBuffer:

@ThreadSafe

public class ConditionBoundedBuffer {

protected final Lock lock = new ReentrantLock();

// CONDITION PREDICATE: notFull (count < items.length)

private final Condition notFull = lock.newCondition();

// CONDITION PREDICATE: notEmpty (count > 0)

private final Condition notEmpty = lock.newCondition();

private static final int BUFFER_SIZE = 100;

@GuardedBy(“lock”) private final T[] items = (T[]) new Object[BUFFER_SIZE];

@GuardedBy(“lock”) private int tail, head, count;

// BLOCKS-UNTIL: notFull

public void put(T x) throws InterruptedException {

lock.lock();

try {

while (count == items.length)

notFull.await();

items[tail] = x;

if (++tail == items.length)

tail = 0;

++count;

notEmpty.signal();

} finally {

lock.unlock();

}

}

// BLOCKS-UNTIL: notEmpty

public T take() throws InterruptedException {

lock.lock();

try {

while (count == 0)

notEmpty.await();

T x = items[head];

items[head] = null;

if (++head == items.length)

head = 0;

–count;

notFull.signal();

return x;

} finally {

lock.unlock();

}

}

}

注意这里使用了signal而不是signalll,能极大减少每次缓存操作中发生的上下文切换和锁请求次数。

使用condition和内置锁和条件队列一样,必须保卫在lock里面。

4 Synchronizer剖析

===============================================================================

看似ReentrantLock和Semaphore功能很类似,每次只允许一定的数量线程通过,到达阀门时:

  • 可以通过 lock或者acquire

  • 等待,阻塞住了

  • 取消tryLock,tryAcquire

  • 可中断的,限时的

  • 公平等待和非公平等待

下面的程序是使用Lock做一个Mutex也就是持有一个许可的Semaphore。

@ThreadSafe

public class SemaphoreOnLock {

private final Lock lock = new ReentrantLock();

// CONDITION PREDICATE: permitsAvailable (permits > 0)

private final Condition permitsAvailable = lock.newCondition();

@GuardedBy(“lock”) private int permits;

SemaphoreOnLock(int initialPermits) {

lock.lock();

try {

permits = initialPermits;

} finally {

lock.unlock();

}

}

// BLOCKS-UNTIL: permitsAvailable

public void acquire() throws InterruptedException {

lock.lock();

try {

while (permits <= 0)

permitsAvailable.await();

–permits;

} finally {

lock.unlock();

}

}

public void release() {

lock.lock();

try {

++permits;

permitsAvailable.signal();

} finally {

lock.unlock();

}

}

}

实际上很多J.U.C下面的类都是基于AbstractQueuedSynchronizer (AQS)构建的,例如CountDownLatch, ReentrantReadWriteLock, SynchronousQueue和FutureTask(java7之后不是了)。AQS解决了实现同步器时设计的大量细节问题,例如等待线程采用FIFO队列操作顺序。AQS不仅能极大极少实现同步器的工作量,并且也不必处理竞争问题,基于AQS构建只可能在一个时刻发生阻塞,从而降低上下文切换的开销,提高吞吐量。在设计AQS时,充分考虑了可伸缩性。

5 AbstractQueuedSynchronizer (AQS)

=================================================================================================

基于AQS构建的同步器类中,最进步的操作包括各种形式的获取操作和释放操作。获取操作是一种依赖状态的操作,并且通常会阻塞。

如果一个类想成为状态依赖的类,它必须拥有一些状态,AQS负责管理这些状态,通过getState,setState, compareAndSetState等protected类型方法进行操作。这是设计模式中的模板模式。

使用AQS的模板如下:

获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。

释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。

boolean acquire() throws InterruptedException {

while (state does not permit acquire) {

if (blocking acquisition requested) {

enqueue current thread if not already queued

block current thread

}

else

return failure

}

possibly update synchronization state

dequeue thread if it was queued

return success

}

void release() {

update synchronization state

if (new state may permit a blocked thread to acquire)

unblock one or more queued threads

}

要支持上面两个操作就必须有下面的条件:

  • 原子性操作同步器的状态位

  • 阻塞和唤醒线程

  • 一个有序的队列

1 状态位的原子操作

这里使用一个32位的整数来描述状态位,前面章节的原子操作的理论知识整好派上用场,在这里依然使用CAS操作来解决这个问题。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer),这里暂且不谈。

2 阻塞和唤醒线程

标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume,并且一直延续了下来。但是这些都是过时的API,而且也是不推荐的做法。

HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

在JDK 5.0以后利用JNI在LockSupport类中实现了此特性。

LockSupport.park() LockSupport.park(Object) LockSupport.parkNanos(Object, long) LockSupport.parkNanos(long) LockSupport.parkUntil(Object, long) LockSupport.parkUntil(long) LockSupport.unpark(Thread)

上面的API中park()是在当前线程中调用,导致线程阻塞,带参数的Object是挂起的对象,这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回,所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三:

  • 其他某个线程调用将当前线程作为目标调用 unpark

  • 其他某个线程中断当前线程;

  • 该调用不合逻辑地(即毫无理由地)返回。

其实第三条就决定了需要循环检测了,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。

3 有序队列

在AQS中采用CHL列表来解决有序的队列的问题。

AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。该队列的操作均通过Lock-Free(CAS)操作.

自己实现的CLH SpinLock如下:

class ClhSpinLock {

private final ThreadLocal prev;

private final ThreadLocal node;

private final AtomicReference tail = new AtomicReference(new Node());

public ClhSpinLock() {

this.node = new ThreadLocal() {

protected Node initialValue() {

return new Node();

}

};

this.prev = new ThreadLocal() {

protected Node initialValue() {

return null;

}

};

}

public void lock() {

final Node node = this.node.get();

node.locked = true;

// 一个CAS操作即可将当前线程对应的节点加入到队列中,

// 并且同时获得了前继节点的引用,然后就是等待前继释放锁

Node pred = this.tail.getAndSet(node);

this.prev.set(pred);

while (pred.locked) {// 进入自旋

}

}

public void unlock() {

final Node node = this.node.get();

node.locked = false;

this.node.set(this.prev.get());

}

private static class Node {

private volatile boolean locked;

}

}

对于入队列(*enqueue):*采用CAS操作,每次比较尾结点是否一致,然后插入的到尾结点中。

do {

pred = tail;

}while ( !compareAndSet(pred,tail,node) );

对于出队列(dequeue):由于每一个节点也缓存了一个状态,决定是否出队列,因此当不满足条件时就需要自旋等待,一旦满足条件就将头结点设置为下一个节点。

AQS里面有三个核心字段:

private volatile int state;

private transient volatile Node head;

private transient volatile Node tail;

其中state描述的有多少个线程取得了锁,对于互斥锁来说state<=1。head/tail加上CAS操作就构成了一个CHL的FIFO队列。下面是Node节点的属性。

独占操作的API都是不带有shared,而共享的包括semaphore和countdownlatch都是使用带有shared字面的API。

一些有用的参考资料:

**java.util.concurrent.locks.AbstractQueuedSynchronizer - **AQS

http://gee.cs.oswego.edu/dl/papers/aqs.pdf论文

http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一个比较全面的另外一个人的解读

http://suo.iteye.com/blog/1329460

http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer

http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-overview.html

http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-clh-and-spin-lock.html

http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-locksupport-and-thread-interrupt.html

独占的就用TRyAcquire, TRyRelease, and isHeldExclusively,共享的就用 tryAcquireShared and TRyReleaseShared. 带有try前缀的方法都是模板方法,AQS用于判断是否可以继续,例如如果tryAcquireShared返回一个负值,那么表示获取锁失败,失败的就需要进入CLH队列,并且挂起线程。

举一个例子,一个简单的闭锁:

@ThreadSafe

public class OneShotLatch {

private final Sync sync = new Sync();

public void signal() {

sync.releaseShared(0);

}

public void await() throws InterruptedException {

sync.acquireSharedInterruptibly(0);

}

private class Sync extends AbstractQueuedSynchronizer {

protected int tryAcquireShared(int ignored) {

// Succeed if latch is open (state == 1), else fail

return (getState() == 1) ? 1 : -1;

}

protected boolean tryReleaseShared(int ignored) {

setState(1); // Latch is now open

return true; // Other threads may now be able to acquire

}

}

}

下面是自己实现的一个Mutex。

/**

  • Lock free的互斥锁,简单实现,不可重入锁

*/

public class Mutex implements Lock {

private static final int FREE = 0;

private static final int BUSY = 1;

private static class LockSync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = 4689388770786922019L;

protected boolean isHeldExclusively() {

return getState() == BUSY;

}

public boolean tryAcquire(int acquires) {

return compareAndSetState(FREE, BUSY);

}

protected boolean tryRelease(int releases) {

if (getState() == FREE) {

throw new IllegalMonitorStateException();

}

setState(FREE);

return true;

}

Condition newCondition() {

return new ConditionObject();

}

}

private final LockSync sync = new LockSync();

public void lock() {

sync.acquire(0);

}

public boolean tryLock() {

return sync.tryAcquire(0);

}

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {

return sync.tryAcquireNanos(1, unit.toNanos(timeout));

}

public void unlock() {

sync.release(0);

}

public Condition newCondition() {

return sync.newCondition();

}

public boolean isLocked() {

return sync.isHeldExclusively();

}

public boolean hasQueuedThreads() {

return sync.hasQueuedThreads();

}

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(0);

}

}

6 AQS实现类

=======================================================================

ReentrantLock


protected boolean tryAcquire(int ignored) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (compareAndSetState(0, 1)) {

owner = current;

return true;

}

} else if (current == owner) {

setState(c+1);

return true;

}

return false;

}

Semaphore和CountDownLatch


protected int tryAcquireShared(int acquires) {

while (true) {

int available = getState();

最后总结

ActiveMQ+Kafka+RabbitMQ学习笔记PDF

image.png

  • RabbitMQ实战指南

image.png

  • 手写RocketMQ笔记

image.png

  • 手写“Kafka笔记”

image

关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

}

} else if (current == owner) {

setState(c+1);

return true;

}

return false;

}

Semaphore和CountDownLatch


protected int tryAcquireShared(int acquires) {

while (true) {

int available = getState();

最后总结

ActiveMQ+Kafka+RabbitMQ学习笔记PDF

[外链图片转存中…(img-drGy9jLZ-1715478676495)]

  • RabbitMQ实战指南

[外链图片转存中…(img-Ur7wAPGQ-1715478676495)]

  • 手写RocketMQ笔记

[外链图片转存中…(img-WrncuIEq-1715478676496)]

  • 手写“Kafka笔记”

[外链图片转存中…(img-g5VRHrL7-1715478676496)]

关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值