zoukankan      html  css  js  c++  java
  • 并发编程(二)

    1. 并发工具类

      1.1 CountDownLatch

      CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

      countDown()实现计数器-1

      await()等待拦截方法,等待计数器为0时再放行,否则则一直阻塞

      getCount()获取当前计数器中计数数量

      

      编写代码测试

    //等待子线程全部执行完毕后在执行主线程
        public static void main(String[] args) {
            System.out.println("主线程开始运行");
            //第一个 子线程
            new Thread(()->{
                System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
        }).start();
            //第二个 子线程
            new Thread(()->{
                System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
            }).start();
            System.out.println("子线程执行完毕,主线程继续执行");
        }

        

      控制台效果

    从下图可看出问题主线程先抢占了资源然后是第一个子线程,但是当第一个子线程运行完后紧接着又是主线程运行最后这才是

    第二个子线程运行,并没有使所有子线程走完后再走主线程

      使用CountDownLatch解决方案

      编写代码测试

    //等待子线程全部执行完毕后在执行主线程
        //监视几个线程就写几个  当前有两个线程所以写2
        private static CountDownLatch countDownLatch=new CountDownLatch(2);
        public static void main(String[] args) throws InterruptedException {
            System.out.println("主线程开始运行");
            //第一个 子线程
            new Thread(()->{
                System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
                //线程数量-1,通知该线程运行完毕
                countDownLatch.countDown();
        }).start();
            //第二个 子线程
            new Thread(()->{
                System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
                //线程数量-1,通知该线程运行完毕
                countDownLatch.countDown();
            }).start();
            //等待,等待计数器中线程数为0时才能继续向下执行
            countDownLatch.await();
            System.out.println("子线程执行完毕,主线程继续执行");
        }

      

      控制台效果

    从下图可看出是所以有子线程走完后再执行的主线程

      1.2 CyclicBarrier

      CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,

        所有进入等待状态的线程被唤醒并继续。 

       CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。 

      CyclicBarrier初始时还可带一个Runnable的参数, Runnable任务在CyclicBarrier的数目达到后,

        所有其它线程被唤醒前被执行。

      编写测试代码

     //创建10个线程
            for (int i=1;i<=10;i++){
                new Thread(()->{
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName()+"准备就绪");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("开始比赛");
                }).start();
            }
        }

      

      控制台效果

      使用CyclicBarrier解决方案

      编写测试代码 

     //设置等待线程数量,当线程数量到达指定数量时,统一向下运行
        private static CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
        public static void main(String[] args) {
            //创建10个线程
            for (int i = 1; i <=10 ; i++) {
                new Thread(()->{
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName()+"准备就绪");
                        //等待
                        cyclicBarrier.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println("开始比赛~");
                }).start();
            }
        }

     

       控制台效果

      1.3 Semaphore

        Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,

      超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也

      可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

      

      编写测试代码

      一共有三个茅坑,每个人上完之后让下一个人上

    for (int i = 1; i <= 10; i++) {
                new Thread(() -> {
                    System.out.println("终于有茅坑了,可以上厕所了");
                }).start();
            }

      控制台效果

      如图是不可以的

      使用Semaphore解决方案

      编写测试代码

    private static Semaphore semaphore=new Semaphore(3);
        public static void main(String[] args) {
            for (int i = 1; i <= 10; i++) {
                new Thread(() -> {
                    if (semaphore.availablePermits()>0){
                        System.out.println(Thread.currentThread().getName()+"终于有茅坑了,可以上厕所了");
                    }else{
                        System.out.println(Thread.currentThread().getName()+"没有茅坑了");
                    }
                    try {
                        //申请资源
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName()+"前面的人终于走了~");
                        //模拟上厕所时间
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName()+"上完了~");
                        //释放资源
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ;
                }).start();
            }
        }

      控制台 效果

      1.4 Exchanger

        可以执行线程的资源交换,线程数量必须为偶数,因为是两两相互交换资源,如果不是偶数默认情况下导致阻塞,

      可以设置交换资源超时时间

      编写测试代码

    private static String str1="资源一";
        private static String str2="资源二";
    
        //构建资源交换
        private static Exchanger<String> stringExchanger=new Exchanger<>();
        public static void main(String[] args) {
            //第一个线程
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str1);
                //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
                try {
                    String  newStr=stringExchanger.exchange(str1);
                    System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            //第二个线程
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2);
                //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
                try {
                    String  newStr=stringExchanger.exchange(str2);
                    System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            //第三个线程
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2);
                //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
                try {
                    String  newStr=stringExchanger.exchange(str2,1000, TimeUnit.MILLISECONDS);
                    System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
                } catch (InterruptedException | TimeoutException e) {
                    e.printStackTrace();
                }
            }).start();
        }

      控制台效果

      2. 线程池

      类似于一个池子,可以存放/管理线程

      2.1 使用线程池的好处
        第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
        第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
        第三:提高线程的可管理性

      2.2 线程池的作用

          线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时

        间,从而提高效率。

          如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池

        中线程的开始、挂起、和中止

      2.3 线程池的分类及使用

          线程池顶级类ThreadPoolExecutor最终实现Executor接口,在JUC包下,通过Executors类可以创建不同类型的线程池,

        分类如下

          1.newScheduledThreadPool 定时任务线程池,可以设置任务时间    

     //构建 一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 1; i <=10 ; i++) {
            //创建线程池
            executorService.execute(()->{
                System.out.println("创建线程池"+Thread.currentThread().getName());
            });
        }

          2.newFixedThreadPool 定长线程池

    //构建线程池对象
            ExecutorService executorService = Executors.newFixedThreadPool(24);
            for (int i = 0; i < 10; i++) {
                //创建线程
                executorService.execute(()->{
                    System.out.println("创建线程:"+Thread.currentThread().getName());
                });
            }
            //线程池停止
            executorService.shutdown();


          3.newSingleThreadExecutor 利用的是单线程,单线程处理任务,一般不用

     //创建一个线程池
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 10; i++) {
                executorService.execute(()->{
                    System.out.println("创建线程:"+Thread.currentThread().getName());
                });
            }


          4.newCachedThreadPool 带缓存的线程池

      //创建一个线程池
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
            for (int i = 0; i < 10; i++) {
                scheduledExecutorService.schedule(()->{
                    System.out.println("创建线程:"+Thread.currentThread().getName());
                },1000, TimeUnit.MILLISECONDS);
            }

      3.所有的线程池分类底层调用的都是ThreadPoolExecutor()构造方法,该构造方法中每一个参数含义
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)

        3.1 corePoolSize代表核心线程池大小,当有任务时,会创建对应线程处理对应任务,当线程到达一定数量后,则会缓存到

          队列当中,不会再次创建新的线程

        3.2 maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程
        3.3 keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
        3.4 unit: 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性

      4.如何确定线程池中创建线程数量
      考虑CPU密集和IO密集,如果不考虑IO情况下,一般是处理器数量+1,如果考虑IO,则处理器*2

        

  • 相关阅读:
    CodeForces
    POJ
    POJ 2260 Error Correction 模拟 贪心 简单题
    POJ
    HDU
    UVA
    CodeForces
    CodeForces
    常见的医学图像成像(总)
    ADNI数据集相关概念整理
  • 原文地址:https://www.cnblogs.com/szhhhh/p/12526487.html
Copyright © 2011-2022 走看看