Concurrency

释放锁
Release/TryRelease
unlock操作实际上调用了AQS的release操作,释放持有的锁

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放锁看看,成功说明锁确实被当前线程拥有,然后看AQS队列头节点是否为空,能否被唤醒,可以的话就唤醒继任节点。
对于独占锁java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int)展示了如何尝试释放锁操作

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
  1. 判断是否当前线程,不是就抛出异常,因为一个线程不能释放另一个线程持有的锁,否则执行2
  2. 将AQS状态位减去要释放的次数,独占锁总是1,如果剩余状态位0(就是没有线程持有锁)
    当前线程是最后一个持有锁的线程,清空AQS独占现场执行
  3. 剩余的状态位写会AQS,没有线程持有锁就返回true,否则false
    tryRelease成功后,release才能检查是否需要唤醒下一个继任节点,前提是AQS队列头节点需要锁(waitStatus!=0),如果头节点需要锁,检查下一个继任节点是否需要锁。
    以前说acquireQueued拿到锁,将当前持有锁的节点设为头节点,如头节点释放锁,需要寻找头节点下一个需要锁继任节点,并唤醒。
private void unparkSuccessor(Node node) {
        //此时node是需要是需要释放锁的头结点

        //清空头结点的waitStatus,也就是不再需要锁了
        compareAndSetWaitStatus(node, Node.SIGNAL, 0);

        //从头结点的下一个节点开始寻找继任节点,当且仅当继任节点的waitStatus<=0才是有效继任节点,否则将这些waitStatus>0(也就是CANCELLED的节点)从AQS队列中剔除  
       //这里并没有从head->tail开始寻找,而是从tail->head寻找最后一个有效节点。
        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);
    }

Condition
大部分为了解决Object.wait/notify/notifyAll难以使用的问题,条件便于在转台条件为true的另一个线程通知他前,挂起线程让他等待。
看看Condition接口定义的方法:
void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); 对应Object.notify void signalAll(); Object.notifyAll

await挂起线程,一旦满足条件就会唤醒,再次获取锁。

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

一个 Condition可以在多个地方被await,需要一个FIFO结构将这些Condition串联,根据需要唤醒一个或多个,通常是所有,所以Condition内部需要一个FIFO队列。

signal/signalAll

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter  = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    Node p = enq(node);
    int c = p.waitStatus;
    if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

signal就是唤醒Condition队列中第一个非Cancelled线程

闭锁(Latch)
一种同步方法,可以延迟线程进度直到线程到达某状态,大门打开之后,所有线程通过。门状态也不能关上了。
CountDownLatch是闭锁的实现,有个计数器,countDown方法对计数器做减操作,await方法等待计数器为0,所有await线程都会阻塞直到计数器为0

CoutndownLatch

public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
public void countDown()
public long getCount()

getCount是获取当前计数

package xylz.study.concurrency.lock;

import java.util.concurrent.CountDownLatch;

public class PerformanceTestTool {

    public long timecost(final int times, final Runnable task) throws InterruptedException {
        if (times <= 0) throw new IllegalArgumentException();
        final CountDownLatch startLatch = new CountDownLatch(1);
        final CountDownLatch overLatch = new CountDownLatch(times);
        for (int i = 0; i < times; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        startLatch.await();
                        //
                        task.run();
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    } finally {
                        overLatch.countDown();
                    }
                }
            }).start();
        }
        //
        long start = System.nanoTime();
        startLatch.countDown();
        overLatch.await();
        return System.nanoTime() - start;
    }

}

两个闭锁,一个startLatch,另一个overLatch,工作准备完后就调用startLatch.countDown打开闭锁,线程执行,第二个闭锁在所有任务执行完后主线程才能继续。第二个闭锁,初始化了一个计数器,每个任务执行完成后就减一,计数器变0,主线程闭锁overLatch拿到信号就可以继续执行。相当于把任务拆分成N份,每一份独立完成任务。

那么看看CountDownLatch是如何实现await方法的内部直接调用了AQS的acquireSharedInterruptibly

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

AQS的tryAcquireShared实现如下,对于闭锁,总应该是1或者-1

public int tryAcquireShared(int acquires) {
    return getState() == 0? 1 : -1;
}

state的值就是初始化的count

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                break;
        }
    } catch (RuntimeException ex) {
        cancelAcquire(node);
        throw ex;
    }
    // Arrive here only if interrupted
    cancelAcquire(node);
    throw new InterruptedException();
}

1.线程节点以共享模式加入AQS的CLH队列中,进行2
2.检查当前节点的前任节点,如果是头结点并且当前闭锁计数大于0,将前节点设置为头结点,唤醒继任节点,返回结束线程阻塞,否则3
3.检查是否应该阻塞,该就阻塞,直到被唤醒,重复2
4.2、3有异常就抛出,结束线程阻塞

有一点要注意一下,这个设置头结点并唤醒继任节点setHeadAndPropagate,由于前面的tryAcquireShared总返回1或者-1,进入setHeadAndPropagate时总是有propagate>=0,所以propagate==1

private void setHeadAndPropagate(Node node, int propagate) {
    setHead(node);
    if (propagate > 0 && node.waitStatus != 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            unparkSuccessor(node);
    }
}

unparkSuccessor方法用于唤醒队列中最前面的节点

private void unparkSuccessor(Node node) {
    //这里,node一般为当前线程所在的结点。
    int ws = node.waitStatus;
    if (ws < 0)//置零当前线程所在的结点状态,允许失败。
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//找到下一个需要唤醒的结点s
    if (s == null || s.waitStatus > 0) {//如果为空或已取消
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。
            if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒
}

那么waitStatus的状态如下

  • CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。 0:新结点入队时的默认状态。

注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。

tryReleaseShared正是采用CAS操作减少计数

public boolean tryReleaseShared(int releases) {

    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

接下来讲CyclicBarrier,如果说CountDownLatch是一次性的,那么CyclicBarrier正好可以循环使用,它允许一组线程相互等待,直到一组任务执行完毕。

package xylz.study.concurrency.lock;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    final CyclicBarrier barrier;

    final int MAX_TASK;

    public CyclicBarrierDemo(int cnt) {
        barrier = new CyclicBarrier(cnt + 1);
        MAX_TASK = cnt;
    }

    public void doWork(final Runnable work) {
        new Thread() {
        
            public void run() {
                work.run();
                try {
                    int index = barrier.await();
                    doWithIndex(index);
                } catch (InterruptedException e) {
                    return;
                } catch (BrokenBarrierException e) {
                    return;
                }
            }
        }.start();
    }

    private void doWithIndex(int index) {
        if (index == MAX_TASK / 3) {
            System.out.println("Left 30%.");
        } else if (index == MAX_TASK / 2) {
            System.out.println("Left 50%");
        } else if (index == 0) {
            System.out.println("run over");
        }
    }

    public void waitForNext() {
        try {
            doWithIndex(barrier.await());
        } catch (InterruptedException e) {
            return;
        } catch (BrokenBarrierException e) {
            return;
        }
    }

    public static void main(String[] args) {
        final int count = 10;
        CyclicBarrierDemo demo = new CyclicBarrierDemo(count);
        for (int i = 0; i < 100; i++) {
            demo.doWork(new Runnable() {

                public void run() {
                    //do something
                    try {
                        Thread.sleep(1000L);
                    } catch (Exception e) {
                        return;
                    }
                }
            });
            if ((i + 1) % count == 0) {
                demo.waitForNext();
            }
        }
    }
}

一共100个任务,每10个一起处理,仅当上一组处理完才能进行下一组。CyclicBarrierDemo构建了count+1任务组,每一个任务执行完毕要等待同组其他任务执行后继续,在剩下50%,30% 0执行其他任务,CyclicBarrierDemo创建了11个任务组,其中有一个任务是为了外界方便挂起主线程。每一个子任务,代码中的await从尾部10开始,执行到0为止,如果有一个被中断,那就会抛出一场InterruptedException执行一次挂一次线程,await返回执行完的索引,索引从任务-1开始的,CyclicBarrier可循坏,每一组完毕,执行下一组。有一个未执行完毕的话,那就唤醒其他线程呗。

CyclicBarrier的API如下:

  • public CyclicBarrier(int parties) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
  • public CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。
  • public int await() throws InterruptedException, BrokenBarrierException 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
  • public int await(long timeout,TimeUnit unit) throws InterruptedException, BrokenBarrierException,TimeoutException 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
  • public int getNumberWaiting() 返回当前在屏障处等待的参与者数目。此方法主要用于调试和断言。
  • public int getParties() 返回要求启动此 barrier 的参与者数目。
  • public boolean isBroken() 查询此屏障是否处于损坏状态。
  • public void reset() 将屏障重置为其初始状态。

接下来是CyclicBarrier.await()的实现片段

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

       int index = --count;
       if (index == 0) {  // tripped
           boolean ranAction = false;
           try {
               final Runnable command = barrierCommand;
               if (command != null)
                   command.run();
               ranAction = true;
               nextGeneration();
               return 0;
           } finally {
               if (!ranAction)
                   breakBarrier();
           }
       }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

剖析一下,原子性意味着要有中断的,这个状态变量就是broken,由于有竞争资源的存在(index/broken)要加锁lock之后变为这样了

  • 检查是否有中断为broken,存在抛出异常,否则进行2
  • 检查当前线程是否中断,是就设置中断位,让其他进入等待的线程知道,唤醒正在等待的线程,以InterruptedException返回,表示线程处理中断,否则3
  • 剩余任务-1,如果剩0,达到终点,执行屏障点任务,创建新Generation,这个过程会唤醒其他所有线程,但其概念现场是屏障点线程,其他线程就在等待。否则4
  • 到4了就说明没到屏障,这个线程就应该park,for循坏要park线程,采用Condition.await(),也就是trip.await(),为何Condition,因为await等待一个条件,条件满足应该都被唤醒,Condition正好满足。所以3中达到屏障点创建Generation一定要唤醒其他线程。
    生成 下一个循坏周期并唤醒其他执行完并等待的线程,count做索引计数,表示有多少线程要执行。isBroken描述的是 generation.broken,就是线程组是否发生异常。设置这个是因为如果一个屏障点发生异常,即使其他线程被唤醒也会循坏等待。没有线程来唤醒这些线程。
private void nextGeneration() {
     trip.signalAll();
     count = parties;
     generation = new Generation();
}

CyclicBarrier还有一个reset方法手动立即将所有线程中断,恢复屏障点,进行下一组任务执行。与重新创建新屏障点相比,维护代价小一些。具体看博客博客讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值