最后
针对以上面试题,小编已经把面试题+答案整理好了
面试专题
除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习
while (isFull())
wait();
boolean wasEmpty = isEmpty();
doPut(v);
if (wasEmpty)
notifyAll();
}
使用notify容易丢失信号,所以大多数情况下用notifyAll,比如take notify,却通知了另外一个take,没有通知put,那么这就是信号丢失,是一种“被劫持的”信号。
因此只有满足下面两个条件,才能用notify,而不是notifyAll:
-
所有等待线程的类型都相同
-
单进单出
和第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();
}
}
===========================================================================================
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里面。
===============================================================================
看似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()返回的原因有三:
其实第三条就决定了需要循环检测了,类似于通常写的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
独占的就用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);
}
}
=======================================================================
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;
}
protected int tryAcquireShared(int acquires) {
while (true) {
int available = getState();
最后总结
ActiveMQ+Kafka+RabbitMQ学习笔记PDF
关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦
}
} else if (current == owner) {
setState(c+1);
return true;
}
return false;
}
protected int tryAcquireShared(int acquires) {
while (true) {
int available = getState();
最后总结
ActiveMQ+Kafka+RabbitMQ学习笔记PDF
[外链图片转存中…(img-drGy9jLZ-1715478676495)]
[外链图片转存中…(img-Ur7wAPGQ-1715478676495)]
[外链图片转存中…(img-WrncuIEq-1715478676496)]
[外链图片转存中…(img-g5VRHrL7-1715478676496)]
关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦