并发编程之 CountDownLatch &CyclicBarrier & Exchanger

CountDownLatch

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化CountDownLatch。由于调用了countDown()方法,所以在当计数器到达零之前,await方法会一直处于阻塞状态。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次—计数无法被重置。

package org.example.juc.CountDownLatch;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);


        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }, "Thread-A").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }, "Thread-B").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }, "Thread-C").start();

        // main线程等待其他线程执行完之后,才能执行
        System.out.println("await...");
        countDownLatch.await();
        System.out.println("done...");

    }
}

执行结果为:

await...
Thread-C running.. 
Thread-A running.. 
Thread-B running.. 
done...

通过结果可以看到,当主线程执行到await() 方法的时候,主线程进行了等待,等待CountDownLatch的计数器归0时,才会执行。注意这里是归零,不是要等待前面几个线程都执行完。我们可以调整下sleep的位置,再测试一下:

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);


        new Thread(() -> {
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName() + " countDown...");
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread-A").start();

        new Thread(() -> {
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName() + " countDown...");
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread-B").start();

        new Thread(() -> {
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName() + " countDown...");
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + " running.. ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread-C").start();

        // main线程等待其他线程执行完之后,才能执行
        System.out.println("await...");
        countDownLatch.await();
        long count = countDownLatch.getCount();
        System.out.println("count=" + count);
        System.out.println("done...");
    }
}

输出结果为:

Thread-A countDown...
Thread-B countDown...
await...
Thread-C countDown...
count=0
done...
Thread-C running.. 
Thread-A running.. 
Thread-B running.. 

可以看到,await方法只是等待CountDownLatch的计数器归0,而不需要等待调用countDown()放的线程执行完。

我们来看一下CountDownLatch的源码:

public class CountDownLatch {
   
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

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

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

    private final Sync sync;


    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }
}

通过源码可以看到,除了我们上面调用的无参数的await方法以外,还有一个带有时间参数的方法,即表示当前线程等待的最长时间,如果在指定时间内countDown没有被归零,或者没有线程没有被中断,则不再等待了。与Object中的wait方法和Condition中的await方法类型。可以响应中断。同样,内部也是通过AQS来实现的。

CyclicBarrier

CyclicBarrier 是另外一种多线程并发控制工具。和CountDownLatch非常类似,它也可以实现线程间的计数等待,但它的功能比CountDownLatch更加复杂且强大。它可以接受一个参数作为barrierAction.所谓barrierAction就是当计数器一次计数完成后,系统会执行的动作。

我们先用一个例子来演示一下:

public class CyclicBarrierTest {

    public static void prepare(){
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println(Thread.currentThread().getName() + " 准备 ..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void doWork() {
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println(Thread.currentThread().getName() + " 工作..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
            System.out.println("done.");
        });

        new Thread(() -> {
            try {
//                prepare();
                System.out.println("A 准备..");
                TimeUnit.SECONDS.sleep(1);
                cyclicBarrier.await();
//                doWork();
                TimeUnit.SECONDS.sleep(1);
                System.out.println("A 工作..");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }

        }, "A").start();

        new Thread(() -> {
            try {
//                prepare();
                TimeUnit.SECONDS.sleep(2);
                System.out.println("B 准备..");
                cyclicBarrier.await();
                TimeUnit.SECONDS.sleep(2);
//                doWork();
                System.out.println("B 工作..");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }, "B").start();

        new Thread(() -> {
            try {
//                prepare();
                TimeUnit.SECONDS.sleep(3);
                System.out.println("C 准备..");
                cyclicBarrier.await();
//                doWork();
                TimeUnit.SECONDS.sleep(3);
                System.out.println("C 工作..");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }, "C").start();
    }

}

结果:

A 准备..
B 准备..
C 准备..
done.
A 工作..
B 工作..
C 工作..
done.

可以看到,准备和工作是分两个阶段的,并不会因为先准备就会先进入工作,依旧需要等待所有任务准备完成之后,再全部进入工作。同时可以看到,CyclicBarrier 是可以重复使用的。

public class CyclicBarrier {
 
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    public CyclicBarrier(int parties) {}
    
    public int getParties() {}
    public int await() throws InterruptedException, BrokenBarrierException {}
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {}

    public boolean isBroken() {}
    public void reset() {}
    public int getNumberWaiting() {}
}

上面是CyclicBarrier的部分源码,构造函数可以只传入parties,或者既传入parties又传入action。await方法也是分为无限等待,以及指定等待之间。

Exchanger

java.util.concurrent 包中的Exchanger类,可用于两个线程之间交换信息。它提供了一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据。

public class Exchanger<V> {
    public Exchanger() {}
    public V exchange(V x) throws InterruptedException {}
    public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {}
}

上面是Exchanger的源码,V代码需要交换数据的类型,exchange则是实际进行数据交换的方法,前面说到,先执行到exchange方法的线程会阻塞等待,直到另一个线程也执行到exchange方法,从而实现数据交换。

构造方法摘要

Exchanger() 创建一个新的 Exchanger。

方法摘要

V exchange(V x) 等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

V exchange(V x, long timeout, TimeUnit unit) 等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。

下面我们做一个示例:

public class ExchangerTest {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    String result = exchanger.exchange("msg from Thread-A : " + i);
                    System.out.println(Thread.currentThread().getName() + "->" + System.currentTimeMillis() + ": content: " + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-A").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    String result = exchanger.exchange("msg from Thread-B : " + i);
                    System.out.println(Thread.currentThread().getName() + "->" + System.currentTimeMillis() + ": content: " + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-B").start();
    }
}

运行结果如下:

Thread-B->1593938468661: content: msg from Thread-A : 0
Thread-A->1593938468661: content: msg from Thread-B : 0
Thread-B->1593938470661: content: msg from Thread-A : 1
Thread-A->1593938470661: content: msg from Thread-B : 1
Thread-B->1593938472661: content: msg from Thread-A : 2
Thread-A->1593938472661: content: msg from Thread-B : 2
Thread-B->1593938474662: content: msg from Thread-A : 3
Thread-A->1593938474662: content: msg from Thread-B : 3
Thread-B->1593938476662: content: msg from Thread-A : 4
Thread-A->1593938476662: content: msg from Thread-B : 4

可以看出,虽然Thread-A设置的sleep时间为1 ,但是会在exchange方法处阻塞,直到Thread-B执行到exchange方法处,然后时间两个线程中的数据交换。

注意:Exchanger适用于两个线程之间数据交换,但是如果在多个线程中使用了同一个Exchanger对象的exchange方法,那么只会优先匹配到先调度到的两个线程,进行数据交换,其他线程则会一直处于阻塞状态。

如下:

public class ExchangerTest {

    public static void main(String[] args) {
        /** 两个线程进行数据交换,如果同时有三个线程进行数据操作,只会由前两个数据进行数据交互。而第三个一直处于等待 */
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start.");
            try {
                String result = exchanger.exchange("I am come from " + Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + " get result from exchanger : " + result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread-A").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start.");
            try {
                TimeUnit.SECONDS.sleep(5);
                String result = exchanger.exchange("I am come from " + Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + " get result from exchanger : " + result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Thread-C").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start.");
            try {
                TimeUnit.SECONDS.sleep(5);
                String result = exchanger.exchange("I am come from " + Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + " get result from exchanger : " + result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Thread-B").start();
    }
}

执行结果为:

并发编程之 CountDownLatch &CyclicBarrier & Exchanger_第1张图片

可以看到,线程A和B进行了数据交换,而程序并没有停止,是因为线程C一直处于阻塞状态下了。

你可能感兴趣的:(并发编程)