Concurrent.util常用的工具类

Concurrent.util常用的工具类

1.CountDownLatch使用:

他经常用于监听某些初始化操作,等初始化执行完毕之后,通知主线程继续工作: 白老师讲zookeeper的例子,调用服务,只有等所有的初始化操作都完成之后,才可以进行使用功能

代码案例:
    public class UseCountDownLatch {
        public static void main(String[] args) {
            // 在线程内部要使用,所以要声明为final(如果不是final,那么是用不了的)
            final CountDownLatch countDown = new CountDownLatch(2);


            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("进入t1线程,等待其他线程处理完成...");
                        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();
        }
    }

这段代码里面CountDownLatch初始化的数字是2,所以得等着两个CountDownLatch都释放了之后,等待着的线程才可以继续执行

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 {
                    Thread.sleep((new Random()).nextInt(5)*1000);
                    System.out.println(name + " 准备OK!");
                    barrier.await(); // 在等着
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "GO!");
            }

        }

        public static void main(String[] args) {
            CyclicBarrier barrier = new CyclicBarrier(3);  //如果传入的参数大于3的话,那么这三个线程都不会继续往下执行
            ExecutorService executor = Executors.newFixedThreadPool(3);

            executor.execute(new Thread(new Runner(barrier, "zhangsan")));
            executor.execute(new Thread(new Runner(barrier, "lisi")));
            executor.execute(new Thread(new Runner(barrier, "wangwu")));

            executor.shutdown();
        }
    }

开启了三个线程,要等这三个线程全部都准备好之后,这三个线程才会继续往下执行,否则都停在了barrier.awit()方法上了

CyclicBarrier:

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地
互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

使用的场景:多台机器去做同一个操作的时候,要求在用一个点执行操作,那么就有可能用到这个.

注意CountDownLatch和CyclicBarrier的区别:

CountDownLatch是属于一个线程等待,其他的n个线程去给我发出通知,然后,我这一个线程执行,只是针对于一个线程.
CyclicBarrier却是中几个线程都是参与阻塞的,这几个线程之后都要在同一点去执行继续的任务

Callable和Future的使用:

这个例子其实就是我们之前实现的Future模式.jdk给予我们一个实现的封装,使用非常简单.

Future模式非常适合在处理很耗时很长的业务逻辑时进行使用,可以有效地减少系统的响应时间,提高系统的吞吐量

Callable接口:Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

老师的案例代码:
    public class UseFuture implements Callable<String>{

        private String param;
        public UseFuture(String param) {
            this.param = param;
        }

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

        public static void main(String[] args) throws InterruptedException, ExecutionException {
            String queryStr = "query";
            //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
            FutureTask<String> futureTask1 = new FutureTask<String>(new UseFuture("query"));

            FutureTask<String> futureTask2 = new FutureTask<String>(new UseFuture("QUERY"));

            //创建一个固定线程的线程池
            ExecutorService executor = Executors.newFixedThreadPool(2);
            //submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值
            Future<?> s1 = executor.submit(futureTask1);   //  任务被提交了
            Future<?> s2 = executor.submit(futureTask2);
            System.out.println("请求完毕"); 
            //单独启动两个前程去执行任务
            System.out.println(s1.get());   //  一定要等FutureTask处理完请求之后才能调用get()方法获取
            System.out.println(s2.get());   //  返回值若为空,代表处理完成

            new Thread(()->{
                try {
                    // 要获取自己的线程执行的结果,是异步地区等待结果的返回  (主线程该怎么走就这么走,不影响)
                    System.out.println("数据1:" + futureTask1.get());  //get()方法是异步去获得数据的
                    System.out.println("数据2:" + futureTask2.get());
                } catch (Exception e) {
                    e.printStackTrace();
                }   //这个get()方法是异步获取的
            }).start();

            try {
                //这里可以做额外的数操作,也就是主程序执行其他的而业务逻辑
                System.out.println("处理实际的业务逻辑....");
                Thread.sleep(7000);
                System.out.println("处理实际的业务逻辑  结束....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executor.shutdown();
        }
    }

仔细分析这个小程序,对比我们之前写的Future模式

信号量:

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

PV(page view) :

网站的总访问量,页面浏览量或点击量,用户没刷新一个就会被记录一次.

UV(unique Visitor) :

访问网站的电脑客户端为一个访客,一般来讲,时间上以00:00-24:00之内相同的ip客户端只记录一次.

QPS(query per second) :

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

RT(response time) :

即请求的相应时间,这个指标非常关键,直接说明前端用户的体验,因此任何系统设计师都想降低rt时间.

当然还涉及cpu,内存,网络,磁盘等情况,更细节的问题很多,如select,update,delete/ps等数据库层面的统计 (ps:每秒访问的次数)

如何去解决高并发的?

1.网络端的.

2.服务端层面的.

3.解决高并发安全问题最重要的不是技术而是业务.在业务上进行严格的模块化.

4.java层面上限制流量 (一般用redis做限流得比较多)

案例代码:
    public class UseSemaphore {
        public static void main(String[] args) {
            ExecutorService executor = 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() {
                    @Override
                    public void run() {
                        try {
                            //获取许可
                            semp.acquire();
                            System.out.println("Accessiong:" + NO + "-----" + semp.getQueueLength());
                            //模拟实际业务逻辑
                            Thread.sleep((long) (Math.random()*10000));
                            //f访问完之后,释放
                            semp.release();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
                executor.execute(run);
            }

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

看打印结果:

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

最开始是5个线程可以同时进行,之后,那个线程执行完毕之后,等待着的线程抢到了时间片就去执行.

容量评估:

一般来说通过开发,运维,测试,以及业务等相关人员,综合处系统的一系列阀值,然后我们根据阀值,如qps,rt等,对系统进行有效的的变更.
一般来讲,我们进行多轮压力测试以后,可以对系统进行峰值评估,采用所谓的80/20原则,即80%的访问请求将在20%的时间内达到.这样我们可以根据系统对应的PV
计算出峰值qps.
峰值qps=(总pv*80%)/(60*60*24*20%)

锁:

在java多线程中,我们知道可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个"同步互斥"工作,他就是
Lock对象,我们主要学习两种锁,重入锁和读写锁.他们具有比synchronized更为强大的功能,并且有嗅探锁定,多路分支等功能.

重入锁:

在需要进行同步的代码部分加上锁,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果
一般使用lock都得有finally CopyOnWrite类底层的锁也是这样实现的

案例代码:
    public class UseReentrantLock {
        private Lock lock = new ReentrantLock();

        public void method01() {
            try {
                lock.lock();
                System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method01");
                Thread.sleep(1000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method01");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //必须要有unLock()
                lock.unlock();
            }
        }
        public void method02() {
            try {
                lock.lock();
                System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method02");
                Thread.sleep(2000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method02");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //必须要有unLock()
                lock.unlock();
            }
        }

        public static void main(String[] args) {

            final UseReentrantLock url = new UseReentrantLock();

            // 方法里面使用了Lock锁,一次就只能有一个线程进行操作
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    url.method01();
                    //url.method02();
                }
            },"t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //url.method01();
                    url.method02();
                }
            },"t2");

            t1.start();
            t2.start();
        }
    }

jdk1.8之前synchronized的性能比Lock的性能要差,但是jdk1.8之后,synchronized做了一系列优化,他的性能跟Lock差不多.Lock比synchronized要更加灵活

锁与等待/通知:

还记得我们在使用synchronized的时候,如果需要多线程间进行写作工作则需要Object的wait()和notify(),notifyAll()方法进行配合工作.
那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition.这个Condition一定是针对具体   某一把锁.也就是只有在锁的基础
上才会产生Condition
之前用synchronized的时候,有时候是需要自己new一个Object()对象出来作为锁,但是这个Object()就只是一个对象,jdk就提供了一个Lock类,里面进行了优化

案例代码:
public class UseCondition {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void method01() {
        try {
            lock.lock();
            System.out.println("当前线程: " + Thread.currentThread().getName() + "进入等待状态...");
            Thread.sleep(3000);
            System.out.println("当前线程: " + Thread.currentThread().getName() + "释放锁...");
            condition.await();
            System.out.println("当前线程: " + Thread.currentThread().getName() + "继续执行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void method02() {
        try {
            lock.lock();
            System.out.println("当前线程: " + Thread.currentThread().getName() + "进入等待状态...");
            Thread.sleep(3000);
            System.out.println("当前线程: " + Thread.currentThread().getName() + "发出唤醒...");
            //并不会释放锁
            condition.signal();   //  相当于  Object  的  notify    (发出唤醒,但是等等这个线程走完代码释放锁之后,另一个线程才能继续执行)
            //Thread.sleep(3000);
            System.out.println("当前线程: " + Thread.currentThread().getName() + "继续执行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        UseCondition uc = new UseCondition();
        new Thread(new Runnable() {
            @Override
            public void run() {
                uc.method01();
            }
        },"t1").start();;
        new Thread(new Runnable() {
            @Override
            public void run() {
                uc.method02();
            }
        },"t2").start();;
    }
}

多Condition:

我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活.可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知

public class UseManyCondition {

    private ReentrantLock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();

    public void m1(){
        try {
            lock.lock();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
            c1.await();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void m2(){
        try {
            lock.lock();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
            c1.await();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void m3(){
        try {
            lock.lock();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
            c2.await();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void m4(){
        try {
            lock.lock();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
            c1.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void m5(){
        try {
            lock.lock();
            System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
            c2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {


        final UseManyCondition umc = new UseManyCondition();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                umc.m1();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                umc.m2();
            }
        },"t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                umc.m3();
            }
        },"t3");
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                umc.m4();
            }
        },"t4");
        Thread t5 = new Thread(new Runnable() {
            @Override
            public void run() {
                umc.m5();
            }
        },"t5");

        t1.start(); // c1
        t2.start(); // c1
        t3.start(); // c2


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

        t4.start(); // c1
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t5.start(); // c2

    }
}

读写锁:

读写锁ReentrantWriteLock,其核心就是实现读写分离的锁.在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁.
之前写的synchronized,ReentrantLock时,我们知道,同一时间内只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁,写锁.
在读锁下,多个线程可以并发地进行访问,但是在写锁的时候,只能一个一个的顺序访问.

口诀:读读共享,写写互斥,读写互斥.

public class UseReentrantReadWriteLock {

    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    private ReadLock readLock = rwLock.readLock();
    private WriteLock writeLock = rwLock.writeLock();


    public void read() {
        try {
            readLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }

    public void write() {
        try {
            writeLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.read();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.read();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.write();
            }
        });
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.write();
            }
        });

        //两个都是读锁  能同时运行
        /*t1.start();  
        t2.start();*/
        //一个是读锁,一个是写锁,
        /*t1.start();
        t3.start();*/
        //两个都是写锁,是互斥的
        t3.start();
        t4.start();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_38200548/article/details/80201146
今日推荐