J.U.C 系列之 Tools

JDK 5.0 开始,java并发大师Doug lea 为我们带来了更高效更应用的java并发API Java.util.concurrent包,简称J.U.C

 

J.U.C 体系由如下五个知识结构构成

 

本节我们首先来介绍其中的并发辅助工具类 Tools

Tools又由几个主要的工具类构成,如下所示‘

一 等待多线程完成的CountDownLatch

CountDownLatch允许一个或者多个线程等待其他线程完成操作。

假如有这样一个需求:我们需要解析一个Excel表里面的多个sheet数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到多有sheet的数据都解析完成之后,程序需要提示解析完成。在这个需求里面要求实现主线程等待所有线程完成sheet的解析操作,最简单的就是使用Thread 的join方法,这里不在讨论,若不清楚可以自行查看,我们这里来看看若使用CountDownLatch如何实现。

代码如下若是

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    /* 
     * CountDownLatch 构造函数接受一个int 类型的参数作为计数器。如果你想等待N个点完成,
       这里就传入N.这里我们测试等待2个点完成
       每次调用countDown方法N就会减一
     */    
    static CountDownLatch c = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
                c.countDown();
                
                System.out.println(2);
                c.countDown();
            }
        }).start();
        
        
        //await 方法会阻塞当前线程,直到计数器N的值变为0,
        //由于countDown方法可以在任何线程执行,所以这里的N个点可以是N个线程
        
        c.await();
        
        System.out.println(3);
    }
}

如果某个sheet的解析处理的异常缓慢,我们不可能让主线程一直等待,所以可以使用另一个带超时等待的await方法await(Long time,TimeUnit unit),这个方法等待指定时间后,就不会继续阻塞当前线程。

注:countDownLatch不能够重新初始化或者修改内部计数器值

二 同步循环屏障 CyclicBarrier

CyclicBarrier的字面量的意思是可循环使用的屏障。它主要做的是,让一组线程达到一个屏障时被阻塞,直到最后一个线程到达屏障点时,屏障才会被打开,所有被屏障拦截的线程才会继续执行。

具体效果图如下所示

 

CyclicBarrier 提供2个构造方法:

public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}

参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时首先执行的任务。

下面我们通过实例演示如何使用CyclicBarrier

package com.wirt;

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

public class CyclicBarrierTest {
 
    static CyclicBarrier c = new CyclicBarrier(2,new A());

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    c.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(1);
                 
            }
        }).start();
        
        try {
            c.await();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        
        System.out.println(2);
    }
    
    static class A implements Runnable{

        @Override
        public void run() {
            System.out.println(3);
        }
        
    }
}

因为设置了拦截数量是2,且两个线程都到达屏障后优先执行A任务,然后在执行到达屏障的任务

 

三 控制并发线程数的Semaphore

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,他通过协调各个线程以保证合理的使用公共资源。

Semaphore 可以用于做流量控制,特别是公共资源的应用场景,比如数据库连接,加入有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取。但是到读到内存后,还是要存储到数据库中,而数据库的连接数只有十个,这是我们必须控制只有十个线程同时获取数据库连接保存数据,否则报错无法获取数据库连接,这个时候,就可以使用Semaphore来做流量控制。示例代码如下

 

 

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

public class SemaphoreTest {
 
    private static final int COUNT =  30; 
    
    private static ExecutorService threadPool = Executors.newFixedThreadPool(COUNT);
    
    private static Semaphore s = new Semaphore(10);
    
    public static void main(String[] args) {
        for(int i =0;i<COUNT;i++){
            threadPool.execute(new Runnable() {
                
                @Override
                public void run() {
                     try {
                         s.acquire(); //获取许可证
                         System.out.println("save data");
                         s.release(); //释放许可证
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        
        threadPool.shutdown();
    }
}

 

在代码中,虽然有30个线程任务在执行,但是只有10个线程可以同时并发执行。

 

 

四 线程间交换数据的Exchanger

Exchanger 是一个用于线程间协作的工具类,用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程通过执行exchanger方法,进行交换数据,如果第一个线程先执行exchanger,它会阻塞等待第二个线程执行exchanger方法,当两个线程都达到同步点时,这两个线程交换数据。

下面通过示例演示Exchanger用法

 

package com.wirt;

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class ExchangerTest { 
    private static final Exchanger<String> exgr = new Exchanger<String>();
    
    private static final ExecutorService  threadPool = Executors.newFixedThreadPool(2);
    
    public static void main(String[] args) {
        threadPool.execute(new Runnable() {
            
            @Override
            public void run() {
                 String A = "银行流水A"; //A录入银行流水数据
                 try {
                    exgr.exchange(A);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        threadPool.execute(new Runnable() {
            
            @Override
            public void run() {
                 String B = "银行流水B"; //B录入银行流水数据
                 try {
                     String A = exgr.exchange(B);
                     System.out.println("A和B数据是否一致:"+A.equals(B)+"; A="+A+"; B="+B);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        threadPool.shutdown();
    }
}

 

五 总结

工具类 使用场景
CountDownLatch 多线程同时解析一个Excel中多个sheet,等到所有sheet解析完成,程序提示完成
CyclicBarrier 多线程计算数据,最后合并计算结果
Semaphore 用于流量控制,特别是公共资源有限情况下的应用场景,例如数据库连接
Exchanger 1 可以用于遗传算法  2 可以用于校对工作

 

参考:《Java并发编程艺术》

工具类之Executors放在下一节

posted on 2016-09-01 11:38  Jerry迎风  阅读(300)  评论(0编辑  收藏  举报

导航