在JDK的并发包里提供了几个非常有用的并发工具类。
其中CountDownLatch,CyclicBarrier和Semaphore工具类提供了一种并发控制的手段。
而Exchanger工具类则提供了在线程间进行交换数据的一种手段。
CountDownLatch允许一个或多个线程等待其他线程完成操作。
定义抽象难理解,直接上测试代码!
import java.util.*;
import java.util.concurrent.CountDownLatch;
public class Main1 {
//主函数,作为程序测试代码的入口
public static void main(String args[]) throws Exception
{
CountDownLatch countDownLatch = new CountDownLatch(2); //使用CountDownLatch,注意构造方法的传入参数
//创建两个测试线程,并启动线程
Thread thread1 = new Thread(new Test(1,countDownLatch));
Thread thread2 = new Thread(new Test(2,countDownLatch));
thread1.start();
thread2.start();
countDownLatch.await(); //CountDownLatch的实现核心,产生关联于计数器的阻塞方式!
System.out.println("主线程main中打印信息永远在所有子线程完成后!");
}
}
//编写线程类Test,实现Runnable接口,并实现其线程方法run
class Test implements Runnable
{
private int number = 0;
private CountDownLatch countDownLatch = null;
//基于构造方法,进行线程类成员变量的初始化设置
public Test(int i,CountDownLatch countDownLatch)
{
this.number = i;
this.countDownLatch = countDownLatch;
}
//实现Runnable下的接口方法,达到线程的目的
public void run()
{
System.out.println("子线程的打印! " + this.number);
this.countDownLatch.countDown();
}
}
程序测试结果,可能有两种情况,分别为:
此时,可以看出,无论两个子线程的执行顺序如何,我们永远保证了main中打印语句在两个线程执行后执行!
具体分析:
CountDownLatch的构造函数接收一个int类型的参数作为计数器,而int具体数值则代表要等待多少个线程点的完成!
例如,程序中初始设置为2,则表示等待的数量为2.
然后就是,countDownLatch的countDown()的方法,每次使用该方法,所维护的计数器数值就减一。
例如,每个子线程的测试逻辑中,都有countdown方法的调用,每次调用,计数器减一。
最后就是,countDownLatch的await()方法,await阻塞于计数器大于一时,并在计数器为0时,结束阻塞!
例如,程序中主线程main中的await方法,只有计数器为0时,才会往下执行!
细节思考:
1.所谓的计数器必须大于或者等于0,只不过当等于0的时候,await方法就会返回,不在阻塞当前线程。
2.countDown()方法,可以用在任何地方,所以,计数器的实际意义,其实是管理countDown()方法的调用次数。通俗来说就是,countDown()方法可以用在多线程中,也可以代表一个线程中的多个执行步骤。
让一组线程到达一个屏障(也就是所谓的同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被该屏障所阻塞的线程才能够继续执行。
测试代码如下:
import java.util.*;
import java.util.concurrent.CyclicBarrier;
public class Main2
{
//程序测试代码的入口
public static void main(String args[])
{
CyclicBarrier cyclicBarrier = new CyclicBarrier(3); //注意构造函数传入的整形值
Thread thread1 = new Thread(new Test2(1, cyclicBarrier));
Thread thread2 = new Thread(new Test2(2, cyclicBarrier));
thread1.start();
thread2.start();
System.out.println("mian到达同步点之前");
try {
cyclicBarrier.await();
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("都到达同步点后继续执行main");
}
}
//线程类,实现runnable接口
class Test2 implements Runnable
{
private int number = 0;
CyclicBarrier cyclicBarrier = null;
//构造函数,初始化该类的成员变量
public Test2(int i,CyclicBarrier cyclicBarrier)
{
this.number = i;
this.cyclicBarrier = cyclicBarrier;
}
public void run()
{
System.out.println(this.number + "到达同步点之前");
try {
cyclicBarrier.await();
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("都到达同步点后继续执行" + this.number);
}
}
程序运行结果如下;
需要知道的是,多线程的具体执行顺序是不可控制的,但是程序结果中我们可以看到,明显的同步点隔离的效果!三个线程都达到同步点,也就是所谓的cyclicbarrier的await方法后,才会继续执行各自同步点时候的代码!
具体说明:
首先,构造函数,同样传入一个int参数,作为计数器,该计数器代表,必须等到N个同步点都完成await的方法调用后,继续执行各个线程同步点之后的代码逻辑!
例子中涉及到的await()方法就是所谓的同步点,必须等到完成计数器所要求的的数量同步点之后,才会解除阻塞!
这里需要注意的是,该工具类还设有一个高级的构造函数,可以指定达到指定数量的同步点后,也就是解除屏障后,优先执行那个线程!public Cyclicbarrier(int parties,Runnable barrierAction).
1.最主要体现在对于计数器的处理上,CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法进行重置。所以CyclicBarrier可以处理相对更加复杂的场景。
semaphore是用来控制同时访问特定资源的线程数量。
借用方腾飞前辈的《java并发编程的艺术》一书中作者的叙述:多年以来,我都觉得从字面上很难理解Semaphore所表达的含义,只能把它比作是控制流量的红绿灯。比如xx马路要限制车流量,只允许同时有一百辆车在这条马路上行驶,其他的必须要在路口等待,所以前面一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能进入xx马路,但是假如前面一百辆车中有五辆车已经离开了xx马路,那么后面就允许有五辆车再进入马路,这个例子中所说的车就是线程,驶入马路就表示线程正在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
具体的理解,还可以参考有关计算机操作系统基础中关于信号量的描述。
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static Semaphore semaphore = new Semaphore(10);
private static ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
public static void main(String args[])
{
for(int i = 0; i < THREAD_COUNT; i++)
{
executor.execute(new Test3(i, semaphore));
}
executor.shutdown();
}
}
//封装线程类,实现runnable接口
class Test3 implements Runnable
{
private int number = 0;
private Semaphore semaphore = null;
public Test3(int number,Semaphore semaphore)
{
this.number = number;
this.semaphore = semaphore;
}
public void run()
{
try {
semaphore.acquire();
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前并发运行编号为: "+this.number);
semaphore.release();
}
}
在代码中,虽然创建30个线程在执行,线程池的容量大小也是30,但是其并发度只有10.
Semaphore的构造方法接收一个整形int的数值,表示可用的许可证数量。new Semaphore(10)表示允许10个线程获取许可证,也就是程序的最大并发数是10,semaphore的用法很简单,首先线程使用acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证!
Exchanger是一个用于线程间协作的工具类,Exchanger用于进行线程间的数据交换。他提供一个同步点,在这个同步点,两个线程可以交换彼此的数据,这两个线程通过exchange()方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点后,这两个线程就可以交换数据。
import java.util.*;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerTest {
private static final Exchanger exchanger = new Exchanger();
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
public static void main(String args[])
{
Thread thread1 = new Thread(new Test4("A", exchanger));
Thread thread2 = new Thread(new Test4("B", exchanger));
executor.execute(thread1);
executor.execute(thread2);
executor.shutdown();
}
}
//封装线程测试类,实现接口方法
class Test4 implements Runnable
{
private String string = null;
private String strcopy = null;
private Exchanger exchanger = null;
public Test4(String string,Exchanger exchanger)
{
this.string = string;
this.exchanger = exchanger;
}
//实现接口方法
public void run()
{
try {
this.strcopy = exchanger.exchange(string);
System.out.println("当前线程发送的字符串:" + string + ",接收的字符串:" + this.strcopy);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序的执行结果显示如下:
如果两个线程中有一个线程没有执行exchange()方法,那么另一个线程会一直等待下去,如果要防止特殊情况发生,避免某个线程持续等待,可以设置等待的最大时长,exchange(String x,long timeout,TimeUtil util);
exchange()方法的简单理解就是,等待两个线程都到达同步点,然后使用该方法,把数据传给另一个线程,并返回从另一个线程中所传送过来的数据。