Java并发工具

1、CountDownLatch

​ CountDownLatch是一个同步工具类,能够使一个线程等待其他一些线程执行完成后,再继续执行!CountDownLatch使用计数器实现。计数器初始值为线程的数量。当每个线程执行完自己任务后,计数器减一。当计数器的值为0时,表示所有的线程都已经完成,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

主要方法列表:

//使用计数器初始化CountDownLatch
public CountDownLatch(int count)
// 使当前线程在计数器归零前一直等待,除非线程被中断。
void await()
// 使当前线程在计数器归零前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减计数器,如果计数到达零,则释放等待的线程。
void countDown()

源码解析:

CountDownDownLatch构造方法:

通过源码可以看到内部类Sync,继承自AbstractQueuedSynchronizer

public class CountDownLatch {
    public CountDownLatch(int count) {
      if (count < 0) throw new IllegalArgumentException("count < 0");
      //其中Sync是内部类
      this.sync = new Sync(count);
    }
    //内部变量
    private final Sync sync;
}

CountDownDownLatch ------ await()方法:

await()方法实际上是调用的AQS的acquireSharedInterruptibly(1);

如果当前线程是中断状态,则抛出InterruptedException异常,否则,尝试获取共享锁

// java.util.concurrent.CountDownLatch.await()
public void await() throws InterruptedException {
    // 调用AQS的acquireSharedInterruptibly()方法
    sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())   //判断是否发生中断
        throw new InterruptedException();
    // 尝试获取锁,如果失败则排队
    if (tryAcquireShared(arg) < 0)   
        doAcquireSharedInterruptibly(arg);
}

CountDownDownLatch ------ countDown()方法:

// java.util.concurrent.CountDownLatch.countDown()
public void countDown() {
    // 调用AQS的释放共享锁方法
    sync.releaseShared(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared()
public final boolean releaseShared(int arg) {
    // 尝试释放共享锁,如果成功了,就唤醒排队的线程
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
使用场景:

CountDownLatch使用示例:百米赛跑模拟

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchTest {
    private static final int RUNNER_COUNT = 10;
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch begin = new CountDownLatch(1);
        final CountDownLatch end = new CountDownLatch(RUNNER_COUNT);
        final ExecutorService exec = Executors.newFixedThreadPool(10);

        for (int i = 0; i < RUNNER_COUNT; i++) {
            final int NO = i + 1;
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                        begin.await();
                        Thread.sleep((long)(Math.random() * 10000));
                        System.out.println("No." + NO + " arrived");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        end.countDown();
                    }
                }
            };
            exec.submit(run);
        }

        System.out.println("Game Start ...");
        begin.countDown();
        end.await();
//        end.await(30, TimeUnit.SECONDS);
        System.out.println("Game Over.");

        exec.shutdown();
    }
}
运行结果:

Game Start ... No.5 arrived No.2 arrived No.10 arrived No.6 arrived No.1 arrived No.8 arrived No.3 arrived No.9 arrived No.7 arrived No.4 arrived Game Over.

从运行结果可以看到,等所有线程运行完后,game over!

2、CyclicBarrier

栅栏,通过它可以让一组线程等待至某个状态之后再全部一起执行,举个例子:还是百米赛跑,需要每个运动准备好了,才能鸣枪开始。

CyclicBarrier不是基于AQS实现的

主要方法列表:

//参与的线程个数,第二个构造方法有一个 Runnable 参数,这个参数是最后一个线程到达时要做的任务
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

源码解析:
public class CyclicBarrier {
    
    //没有自定义同步器,而是定义一个Generation类,里面包含一个broker属性
    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  触发时要运行的命令,即执行额外的操作 */
    //在构造函数的时候赋值,它的用处是当count清零后,
    //也就是所有线程都执行到await()方法,其中一个线程拿到锁后,是否执行额外的操作,还是继续执行自身逻辑。
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     * 记录还有多少在等待
     */
    private int count;
  
}

入口方法await():

//重载方法1
public int await() throws InterruptedException, BrokenBarrierException {
        try {
            //调用核心方法
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
 }
  
//重载方法2
public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        //调用核心方法
        return dowait(true, unit.toNanos(timeout));
}

核心方法dowait():

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();
            }
            //打破屏障,本身线程让count减一
            int index = --count;
            //判断是否打到临界状态
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //检测额外动作时候有,如果不为空,则执行额外动作
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //进入下一个纪元,方法内部有调用 signalAll()唤醒所有阻塞的线程
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
            //未达到临界状态,开始阻塞,自旋
            // loop until tripped, broken, interrupted, or timed out
            //自旋直到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 {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }
                // 如果屏障已经broken了,则抛出异常  会导致状态异常
                if (g.broken)
                    throw new BrokenBarrierException();
                if (g != generation)// 如果进入下一个纪元了,则返回剩余未进入等待状态的线程数
                    return index;
                //超时了关闭屏障
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            //释放锁
            lock.unlock();
        }
}
 
//关闭屏障  打断状态
private void breakBarrier() {
        generation.broken = true;  // 设置broken标识
        count = parties;  // 重置计数器
        trip.signalAll();  // 唤醒所有阻塞线程
    }

// 开启下一代,重置所有参数
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll(); // 唤醒所有等待的线程
    // set up next generation
    count = parties; // 计数器重置
    generation = new Generation(); // 重新实例化 纪元
}
使用场景:

等所有线程到达栅栏后,在往下执行

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

class MyThread extends Thread {
    private CyclicBarrier cb;
    public MyThread(String name, CyclicBarrier cb) {
        super(name);
        this.cb = cb;
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName() + " going to await");
        try {
            cb.await();
            System.out.println(Thread.currentThread().getName() + " continue");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class CyclicBarrierDemo {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        CyclicBarrier cb = new CyclicBarrier(3, new Thread("barrierAction") {
            public void run() {
                System.out.println(Thread.currentThread().getName() + " barrier action");
                
            }
        });
        MyThread t1 = new MyThread("t1", cb);
        MyThread t2 = new MyThread("t2", cb);
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName() + " going to await");
        cb.await();
        System.out.println(Thread.currentThread().getName() + " continue");

    }
}
运行结果:

t2 going to await main going to await t1 going to await t1 barrier action t1 continue t2 continue main continue

从运行结果可以看出,当所有线程到达await之后,所有线程就继续进行后续continue的操作。

CyclicBarrier 和 CountDownLatch差别:

CountDownLatch和CyclicBarrier都能实现线程之间的等待,但是它们的侧重点不同:

​ CountDownLatch一般用于某个线程等待其他线程执行完后,它才执行;

​ CyclicBarrier一般用于一组线程互相等待至某个状态,然后这组线程再同时执行;

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

3、Semaphore(信号量)

​ Semaphore通过使用计数器来控制对共享资源的访问。 如果计数器大于0,则允许访问。 如果为0,则拒绝访问,

​ 在并发编程中,可以控制返访问同步代码的线程数量

​ Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。

​ Semaphore内部类实现AQS。

Semaphore主要方法

//从信号量获取一个许可,如果无可用许可前将一直阻塞等待,
void acquire() 
//获取指定数目的许可,如果无可用许可前也将会一直阻塞等待
void acquire(int permits)
//从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞
boolean tryAcquire()
//尝试获取指定数目的许可,如果无可用许可直接返回false
boolean tryAcquire(int permits) 
//在指定的时间内尝试从信号量中获取许可,如果在指定的时间内获取成功,返回true,否则返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
//释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,如:初始permits为1,调用了两次release,最大许可会改变为2
void release()
//获取当前信号量可用的许可
int availablePermits()
源码分析:
//信号数量以及是否公平锁
public Semaphore(int permits) {
        sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

acquire方法:

可以看到和CountDownLatch一样用的是AQS的acquireSharedInterruptibly方法

public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
// java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())   //判断是否发生中断
        throw new InterruptedException();
    // 尝试获取锁,如果失败则排队
    if (tryAcquireShared(arg) < 0)   
        doAcquireSharedInterruptibly(arg);
}
  • doAcquireSharedInterruptibly(arg) 调用tryAcquireShared()方法尝试获取信号量。
  • 如果没有可用信号,将当前线程加入等待队列并挂起

  tryAcquireShared()方法被Semaphore的内部类NonfairSync和FairSync重写,实现有一些区别。

 FairSync.tryAcquireShared(int acquires) :

	先调用hasQueuedPredecessors()方法,判断队列中是否有等待线程。如果有,直接返回-1,表示没有可用信号
	队列中没有等待线程,再使用CAS尝试更新state,获取信号
protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

NofairSync.tryAcquireShared(int acquires):

非公平锁对于信号的获取是直接使用CAS进行尝试的。

protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

release 方法:

  • 释放一个信号

  • release方法间接的调用了Sync的tryReleaseShared方法,该方法基于cas 将state的值设置为state+1,一直循环确保CAS操作成功,成功后返回true。

public void release() {
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }
使用场景:

4个人一起去上厕所,不过厕所只有2个位置,不能四个人一起用,这种场景就可以用到Semaphore。

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    public static void main(String[] args) throws InterruptedException {
        //厕所里只有两个坑位,初始化信号量总数为2。
        Semaphore washbasin = new Semaphore(2);
        List<Thread> threads = new ArrayList<>(4);
        threads.add(new Thread(new People(washbasin, "二筒")));
        threads.add(new Thread(new People(washbasin, "小鸡")));
        threads.add(new Thread(new People(washbasin, "八万")));
        threads.add(new Thread(new People(washbasin, "三条")));
        for (Thread thread : threads) {
            thread.start();
            Thread.sleep(50);
        }
        for (Thread thread : threads) {
            thread.join();
        }
    }
}

class People implements Runnable {
    private Semaphore washroom;
    private String name;

    public People(Semaphore washroom, String name) {
        this.washroom = washroom;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            Random random = new Random();
            washroom.acquire();
            System.out.println(
                    sdf.format(new Date()) + " " + name + " 开始蹲坑了");
            Thread.sleep((long) (random.nextDouble() * 2000) + 3000);
            System.out.println(
                    sdf.format(new Date()) + " " + name + " 蹲坑结束!");
            washroom.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
运行结果:

14:24:43.235 小鸡 开始蹲坑了
14:24:43.235 二筒 开始蹲坑了
14:24:46.847 小鸡 蹲坑结束!
14:24:46.849 八万 开始蹲坑了
14:24:47.492 二筒 蹲坑结束!
14:24:47.492 三条 开始蹲坑了
14:24:50.188 八万 蹲坑结束!
14:24:50.744 三条 蹲坑结束!