java 多线程协同_JAVA并发梳理(三) 多线程协作方式及实现原理

线程间的基本协作方式请参考 多线程协作方式。

在此基础上,结合源码梳理一下每种方式的实现原理。

Synchronized

·synchronized·是Java原生的互斥同步锁,使用方便,对于·synchronized·修饰的方法或同步块,无需再显式释放锁。·synchronized·底层是通过·monitorenter·和·monitorexit·两个字节码指令来实现加锁解锁操作的。

除Synchronized之外的线程同步工具

除了Synchronized,其余的线程协作方式都是在Java API层面的。这些工具基本都依赖于AbstractQueuedSynchronizer。AbstractQueuedSynchronizer是Java并发很强大的同步器,它内部维护了一个等待锁的双向链表。链表节点是自定义的Node(定义参考以下源码),每个Node包含了一个线程对象,它的前后指针,以及节点的状态。对于线程的阻塞和唤醒又是基于LockSupport提供的park, unpark实现。

LockSupport,是线程阻塞的原语。详细请参考 JAVA并发梳理(一)LockSupport。

static final class Node {

/** Marker to indicate a node is waiting in shared mode */

static final Node SHARED = new Node();

/** Marker to indicate a node is waiting in exclusive mode */

static final Node EXCLUSIVE = null;

/** waitStatus value to indicate thread has cancelled */

static final int CANCELLED = 1;

/** waitStatus value to indicate successor's thread needs unparking */

static final int SIGNAL = -1;

/** waitStatus value to indicate thread is waiting on condition */

static final int CONDITION = -2;

/**

* waitStatus value to indicate the next acquireShared should

* unconditionally propagate

*/

static final int PROPAGATE = -3;

/**

* Status field, taking on only the values:

* SIGNAL: The successor of this node is (or will soon be)

* blocked (via park), so the current node must

* unpark its successor when it releases or

* cancels. To avoid races, acquire methods must

* first indicate they need a signal,

* then retry the atomic acquire, and then,

* on failure, block.

* CANCELLED: This node is cancelled due to timeout or interrupt.

* Nodes never leave this state. In particular,

* a thread with cancelled node never again blocks.

* CONDITION: This node is currently on a condition queue.

* It will not be used as a sync queue node

* until transferred, at which time the status

* will be set to 0. (Use of this value here has

* nothing to do with the other uses of the

* field, but simplifies mechanics.)

* PROPAGATE: A releaseShared should be propagated to other

* nodes. This is set (for head node only) in

* doReleaseShared to ensure propagation

* continues, even if other operations have

* since intervened.

* 0: None of the above

*

* The values are arranged numerically to simplify use.

* Non-negative values mean that a node doesn't need to

* signal. So, most code doesn't need to check for particular

* values, just for sign.

*

* The field is initialized to 0 for normal sync nodes, and

* CONDITION for condition nodes. It is modified using CAS

* (or when possible, unconditional volatile writes).

*/

volatile int waitStatus;

/**

* Link to predecessor node that current node/thread relies on

* for checking waitStatus. Assigned during enqueuing, and nulled

* out (for sake of GC) only upon dequeuing. Also, upon

* cancellation of a predecessor, we short-circuit while

* finding a non-cancelled one, which will always exist

* because the head node is never cancelled: A node becomes

* head only as a result of successful acquire. A

* cancelled thread never succeeds in acquiring, and a thread only

* cancels itself, not any other node.

*/

volatile Node prev;

/**

* Link to the successor node that the current node/thread

* unparks upon release. Assigned during enqueuing, adjusted

* when bypassing cancelled predecessors, and nulled out (for

* sake of GC) when dequeued. The enq operation does not

* assign next field of a predecessor until after attachment,

* so seeing a null next field does not necessarily mean that

* node is at end of queue. However, if a next field appears

* to be null, we can scan prev's from the tail to

* double-check. The next field of cancelled nodes is set to

* point to the node itself instead of null, to make life

* easier for isOnSyncQueue.

*/

volatile Node next;

/**

* The thread that enqueued this node. Initialized on

* construction and nulled out after use.

*/

volatile Thread thread;

/**

* Link to next node waiting on condition, or the special

* value SHARED. Because condition queues are accessed only

* when holding in exclusive mode, we just need a simple

* linked queue to hold nodes while they are waiting on

* conditions. They are then transferred to the queue to

* re-acquire. And because conditions can only be exclusive,

* we save a field by using special value to indicate shared

* mode.

*/

Node nextWaiter;

/**

* Returns true if node is waiting in shared mode.

*/

final boolean isShared() {

return nextWaiter == SHARED;

}

/**

* Returns previous node, or throws NullPointerException if null.

* Use when predecessor cannot be null. The null check could

* be elided, but is present to help the VM.

*

* @return the predecessor of this node

*/

final Node predecessor() throws NullPointerException {

Node p = prev;

if (p == null)

throw new NullPointerException();

else

return p;

}

Node() { // Used to establish initial head or SHARED marker

}

Node(Thread thread, Node mode) { // Used by addWaiter

this.nextWaiter = mode;

this.thread = thread;

}

Node(Thread thread, int waitStatus) { // Used by Condition

this.waitStatus = waitStatus;

this.thread = thread;

}

}

AbstractQueuedSynchronizer还提供了几个钩子方法供其子类覆写来提供各自的实现方式,比如:

protected boolean tryAcquire(int arg) {

throw new UnsupportedOperationException();

}

protected boolean tryRelease(int arg) {

throw new UnsupportedOperationException();

}

protected int tryAcquireShared(int arg) {

throw new UnsupportedOperationException();

}

ReentrantLock, Semaphore, CountDownLatch等内部都是通过继承AbstractQueuedSynchronizer来实现的。

关于更多关于AbstractQueuedSynchronizer的实现,请参考 Java AQS AbstractQueuedSynchronizer。

在了解AbstractQueuedSynchronizer的基础上,我们来看看这些高级的线程协作是怎么做的。

1. ReentrantLock

ReentrantLock做为API层面的互斥锁,需要显式地去加锁解锁。底层是基于AbstractQueuedSynchronizer实现的。

ReentrantLock 是之前的一篇总结。下面再做点补充:

关于与ReentrantLock配合使用的Condition。其实是一个ConditionObject对象。类ConditionObject也是定义在AbstractQueuedSynchronizer中的。每个ConditionObject都自己维护一个Node链表,表示等待在这个条件上的线程队列。值得注意的是,当调用signal()/signalAll()之后,这些被通知的线程并没有立马等到锁,而是会被加入到该ConditionObject对象所关联的ReentrantLock的等待队列中。

结合线程从运行到等待到阻塞的状态变化过程,其实跟上面的实现是呼应的。

final ConditionObject newCondition() {

return new ConditionObject();

}

理解并仔细体会一下ReentrantLock和各个ConditionObject对象背后的队列。

2. Semaphore

如前面所说,首先Semaphore内部也是基于AbstractQueuedSynchronizer。它也提供了公平和非公平的实现。跟ReentrantLock的区别在于,它含有多个许可。

一个简单的测试类:

public class SemaphoreTest {

private static Semaphore semaphore = new Semaphore(5);

public static void main(String[] args) {

for(int i = 0; i < 10; i++) {

new Thread(new Driver(i)).start();

}

}

static class Driver implements Runnable{

int i;

Driver(int i) {

this.i = i;

}

@Override

public void run() {

try {

semaphore.acquire();

System.out.println("Driver " + i + " is driving ... ");

Thread.sleep(1000);

} catch (InterruptedException e) {

} finally {

semaphore.release();

System.out.println("Driver " + i + " stops ... ");

}

}

}

}

3. CountDownLatch

类似计数器的功能,比如某个线程任务,需要等待其他 N 个线程任务执行完才能执行。内部实现基于AbstractQueuedSynchronizer。

public class CDLTest {

private static CountDownLatch cdl = new CountDownLatch(10);

private static ThreadLocal r = new ThreadLocal() {

@Override

protected Random initialValue() {

return new Random(1000);

}

};

public static void main(String[] args) throws InterruptedException {

for(int i = 0; i < 10; i++) {

new Thread(new Phase(i)).start();

}

cdl.await();

System.out.println("Main continues .... ");

}

static class Phase implements Runnable {

int i;

Phase(int i) {

this.i = i;

}

@Override

public void run() {

try {

Thread.sleep(r.get().nextInt(1000));

System.out.println("Phase " + i + " is ready. ");

cdl.countDown();

} catch (InterruptedException e) {

}

}

实际上,这几种基于AbstractQueuedSynchronizer的工具,基本原理都是等待队列,然后各自实现不同的方式来acquire和release,里面有个int State。

4. ReentrantReadWriteLock

基于AbstractQueuedSynchronizer实现。每个ReentrantReadWriteLock分别包含一个WriteLock对象和一个ReadLock。也分别有公平锁和非公平锁的实现。

private final ReentrantReadWriteLock.ReadLock readerLock;

/** Inner class providing writelock */

private final ReentrantReadWriteLock.WriteLock writerLock;

public ReentrantReadWriteLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

readerLock = new ReadLock(this);

writerLock = new WriteLock(this);

}

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }

内部还是维护一个等待队列(链表)。根据节点状态判断它是在请求读还是写。

final boolean isShared() {

return nextWaiter == SHARED;

}

public final Collection getQueuedThreads() {

ArrayList list = new ArrayList();

for (Node p = tail; p != null; p = p.prev) {

Thread t = p.thread;

if (t != null)

list.add(t);

}

return list;

}

public final Collection getExclusiveQueuedThreads() {

ArrayList list = new ArrayList();

for (Node p = tail; p != null; p = p.prev) {

if (!p.isShared()) {

Thread t = p.thread;

if (t != null)

list.add(t);

}

}

return list;

}

final boolean tryWriteLock() {

Thread current = Thread.currentThread();

int c = getState();

if (c != 0) {

int w = exclusiveCount(c);

if (w == 0 || current != getExclusiveOwnerThread())

return false;

if (w == MAX_COUNT)

throw new Error("Maximum lock count exceeded");

}

if (!compareAndSetState(c, c + 1))

return false;

setExclusiveOwnerThread(current);

return true;

}

final boolean tryReadLock() {

Thread current = Thread.currentThread();

for (;;) {

int c = getState();

if (exclusiveCount(c) != 0 &&

getExclusiveOwnerThread() != current)

return false;

int r = sharedCount(c);

if (r == MAX_COUNT)

throw new Error("Maximum lock count exceeded");

if (compareAndSetState(c, c + SHARED_UNIT)) {

if (r == 0) {

firstReader = current;

firstReaderHoldCount = 1;

} else if (firstReader == current) {

firstReaderHoldCount++;

} else {

HoldCounter rh = cachedHoldCounter;

if (rh == null || rh.tid != getThreadId(current))

cachedHoldCounter = rh = readHolds.get();

else if (rh.count == 0)

readHolds.set(rh);

rh.count++;

}

return true;

}

}

}

5. CyclicBarrier

内部是基于ReentrantLock和Condition来实现的。N 个线程互相等待,任何一个线程达到某个状态之前,所有的线程都必须等待。必须等到所有线程都达到某个状态,这 N 个线程才能继续执行。应用场景如,有100个人要去旅游,每到达10个人就开一个团出发。

private static class Generation {

boolean broken = false;

}

/** The lock for guarding barrier entry */

private final ReentrantLock lock = new ReentrantLock();

/** Condition to wait on until tripped */

private final Condition trip = lock.newCondition();

/** The number of parties */

private final int parties;

/* The command to run when tripped */

private final Runnable barrierCommand;

/** The current generation */

private Generation generation = new Generation();

CyclicBarrier 还可以接收一个参数作为barrierCommand,指定当计数器一次计数完成之后,系统会执行的动作。

CyclicBarrier.await() 方法可能会抛出两个异常。一个是InterruptedException ,也就是在等待过程中,线程被中断,这使得线程在等待是依然可以响应外部紧急事件;另一个异常是CyclicBarrier特有的BrokenBarrierException。 一旦遇到这个一次杨,则表示当前的栅栏已经破损了,可能系统已经没有办法等待所有的线程到齐了。例如,第五个线程被中断,它会得到一个InterruptedException, 而其他9个线程会分别得到BrokenBarrierException。这个异常可以避免其他9个线程进行永久的、无谓的等待。

public class CBTest {

private static CyclicBarrier cb = new CyclicBarrier(5, new Runnable() {

@Override

public void run() {

System.out.println("5 people is enough. Setting out .... ");

}

});

private static ThreadLocal r = new ThreadLocal() {

@Override

protected Random initialValue() {

return new Random(2000);

}

};

public static void main(String[] args) {

for(int i = 0; i < 100; i ++) {

new Thread(new Visitor(i)).start();

}

}

static class Visitor implements Runnable{

int i;

public Visitor(int i) {

this.i = i;

}

@Override

public void run() {

try {

Thread.sleep(r.get().nextInt(2000));

System.out.println(i + " is waiting .... ");

cb.await();

System.out.println(i + " sets out .... ");

} catch (InterruptedException e) {

} catch (BrokenBarrierException e) {

}

}

}

}

了解一下多线程各种协作方式的实现,可以发现其实每个工具之所以发挥相应的作用都是有其原理的,不是什么神秘的东西。每次看一遍都能有些不一样的收获。下一步希望能把Node中定义的那几种State在acquire, release中发挥的作用搞搞清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值