java线程安全之Concurrent.util常用类(十六)

Concurrent.util常用类

CyclicBarrier使用:

假设有只有的一个场景:每个线程代表一个跑步运动员,当运动员都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。

工作中场景:比如你多台机器想同时准备好了,再去干分布式计算等等。
案例:

public class UseCyclicBarrier {

    static class Runner implements Runnable {  
        private CyclicBarrier barrier;  
        private String name;  

        public Runner(CyclicBarrier barrier, String name) {  
            this.barrier = barrier;  
            this.name = name;  
        }  
        @Override  
        public void run() {  
            try {  
                //随机休眠0-5秒
                Thread.sleep(1000 * (new Random()).nextInt(5));  
                System.out.println(name + " 准备OK."); 
                //等待
                barrier.await();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (BrokenBarrierException e) {  
                e.printStackTrace();  
            }  
            System.out.println(name + " Go!!");  
        }  
    } 

    public static void main(String[] args) throws IOException, InterruptedException {  
        CyclicBarrier barrier = new CyclicBarrier(3);  // 3 
        //3个固定的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);  

        executor.submit(new Thread(new Runner(barrier, "hfbin-01")));  
        executor.submit(new Thread(new Runner(barrier, "hfbin-02")));  
        executor.submit(new Thread(new Runner(barrier, "hfbin-03")));  

        executor.shutdown();  
    }  

}  

打印答案(注意看打印的顺序):

hfbin-01 准备OK.
hfbin-02 准备OK.
hfbin-03 准备OK.
hfbin-03 Go!!
hfbin-01 Go!!
hfbin-02 Go!!

CountDownLacth使用:

他经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工。

案例:

public class UseCountDownLatch {

    public static void main(String[] args) {

        final CountDownLatch countDown = new CountDownLatch(2);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("进入线程t1" + "等待其他线程处理完成...");
                    //等待countDown发出通知即可继续执行下面代码
                    countDown.await();
                    System.out.println("t1线程继续执行...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t2线程进行初始化操作...");
                    Thread.sleep(3000);
                    System.out.println("t2线程初始化完毕,通知t1线程继续...");
                    countDown.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t3线程进行初始化操作...");
                    Thread.sleep(4000);
                    System.out.println("t3线程初始化完毕,通知t1线程继续...");
                    countDown.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();
        t3.start();



    }
}

打印答案:

进入线程t1等待其他线程处理完成...
t2线程进行初始化操作...
t3线程进行初始化操作...
t2线程初始化完毕,通知t1线程继续...
t3线程初始化完毕,通知t1线程继续...
t1线程继续执行...

t1使用 await() 这个方法会处于阻塞状态 需要等待t2 t3 countDown()执行完成后才能继续执行 t1 被阻塞后面的代码

也就是:t1 需要等 t2 t3 执行完成之后才能执行

注意:CountDownLatch countDown = new CountDownLatch(2); 这里用到多少个countDown(),CountDownLatch()里面参数就写多少个。

注意:CyclicBarrier和CountDownLacth的区别

CountDownLacth 属于一个线程等待,其他n个线程发出通知 ,然后再执行等待的线程。(针对一个线程)

CyclicBarrier 多个线程参与阻塞。(针对多个线程)

Callable和Future使用

这个例子其实是我们之前实现的Future 模式 。jdk给予我们一个实现的封装类,使用非常简单。
Future 模式非常适合在处理很耗时很长的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量。

案例

public class UseFuture implements Callable<String>{
    private String para;

    public UseFuture(String para){
        this.para = para;
    }

    /**
     * 这里是真实的业务逻辑,其执行可能很慢
     */
    @Override
    public String call() throws Exception {
        //模拟执行耗时
        Thread.sleep(5000);
        String result = this.para + "处理完成";
        return result;
    }

    //主控制函数
    public static void main(String[] args) throws Exception {
        String queryStr = "query-1";
        String queryStr2 = "query-2";
        //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
        FutureTask future = new FutureTask(new UseFuture(queryStr));

        FutureTask future2 = new FutureTask(new UseFuture(queryStr2));
        //创建一个固定线程的线程池且线程数为2,
        ExecutorService executor = Executors.newFixedThreadPool(2);
        //这里提交任务future,则开启线程执行RealData的call()方法执行
        //submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值

        //执行完成后 f1 f2 都会为空
        Future f1 = executor.submit(future);        //单独启动一个线程去执行的
        Future f2 = executor.submit(future2);
        System.out.println("f1 请求完毕  = "+f1.get());
        System.out.println("请求完毕");

        try {
            //这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
            System.out.println("处理实际的业务逻辑...");
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
        System.out.println("数据:" + future.get());
        System.out.println("数据:" + future2.get());

        executor.shutdown();
    }

}

打印结果

f1 请求完毕  = null
请求完毕
处理实际的业务逻辑...
数据:query-1处理完成
数据:query-2处理完成

Semaphore 使用

在Semaphone信号量非常适合高并发访问,新系统在上线之前,要对系统的访问量进行评估,当然这个值肯定不是随便拍拍脑袋就能想出来的,是经过以往的经验、数据、历年的访问量,己经推广力度讲行一个合理的评估,当然评估标推不能太大也不能太小,太大的话投入的资源达不到实际效果,纯粹浪费资源,太小的话,某个时间点一个高峰值的访问量上来直接可以压垮系统
相关概念:

PV(view)网站的总访何量,页面浏览量或点击量,用户每刷新一,次就会被记录一次。

UV (unique Visitor)访网网站的一台申客户端为一个访客,一般来讲,时间上以00:00-24:00之内相同ip的客户端只记录一次。

QPS(query per second)即每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。我们通过qps可以非常直观的了解当前系统业务情况,一旦当前超过所设定的预警阀值,可以考虑加机器对集群扩容,以免压力过大导致宕机,可以根据前期的压力测试得到估值,在结合后期综合运维清况,估算出值。

RT (response time)即请求的响应时间,这个指标非常关键,直接说明前端用户的体验,因此任何系统都想降低rt时间。当然还涉及cpu、内存、网络、磁盘等情况,更细节的问很多,如select、update、delete/ps等数据库层面的统计。

容量评估:一般来说通过开发、运维、测试、以及业务等相关人员.綜合出系统的一系列阈值,然后我们根据关键阈值如qps、rt等,对系统讲行有效的变更。一般来讲.我们讲行多轮压力测试以后,可以对系统讲行峰值评估,采用所谓的80/20原则,即80%的访网请求将在20%的时间内达到。这样我们可以根据系统对应PV计算出峰值 qps。

峰值qps= (总PV ×80%)/(60 × 60× 24 ×20%)

然后在将总的峰值qps除以单台机器所能承受的最高的qps值,就是所需要机器的数量:机器数=总的峰值qps /压测得出的单机极限qps

当然不排除系统在上线前进行大型促销活动,或者双十一、双十二热点事件、遭受到DDos攻击等情况,系统的开发和运维人员急需要了解当前系统运行的状态和负载情况,一般都会有后台系统去维护。

Semaphone可以控制系统的流量:
拿到信号量的钱程可以讲入,否则就等待.通过acquire()和rekease()获取和释放访同许可。

案例:

public class UseSemaphore {  

    public static void main(String[] args) {  
        // 线程池  
        ExecutorService exec = Executors.newCachedThreadPool();  
        // 只能5个线程同时访问  
        final Semaphore semp = new Semaphore(5);  
        // 模拟20个客户端访问  
        for (int index = 0; index < 20; index++) {  
            final int NO = index;  
            Runnable run = new Runnable() {  
                public void run() {  
                    try {  
                        // 获取许可  
                        semp.acquire();  
                        System.out.println("Accessing: " + NO);  
                        //模拟实际业务逻辑
                        Thread.sleep((long) (Math.random() * 10000));  
                        // 访问完后,释放  
                        semp.release();  
                    } catch (InterruptedException e) {  
                    }  
                }  
            };  
            exec.execute(run);  
        } 

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //System.out.println(semp.getQueueLength());

        // 退出线程池  
        exec.shutdown();  
    }  

}  

打印的结果:

Accessing: 0
Accessing: 1
Accessing: 2
Accessing: 3
Accessing: 4
Accessing: 5
Accessing: 6
Accessing: 7
Accessing: 8
Accessing: 9
Accessing: 10
Accessing: 11
Accessing: 12
Accessing: 13
Accessing: 14
Accessing: 15
Accessing: 16
Accessing: 19
Accessing: 17
Accessing: 18

源代码:https://github.com/hfbin/Thread_Socket/tree/master/Thread/concurrent019

你可能感兴趣的:(java,线程,线程安全)