• 并发工具类和线程池


    工具类

    CountDownLatch

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

    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchDemo {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("主线程启动---->等待子线程执行完毕");
            //代表等待两个线程执行完主线程才继续执行
            CountDownLatch countDownLatch=new CountDownLatch(2);
            new Thread(() ->
            {
                System.out.println("第一个子线程" + Thread.currentThread().getName() + "正在执行");
                countDownLatch.countDown();
                System.out.println("第一个子线程" + Thread.currentThread().getName() + "执行完毕");
            }).start();
            new Thread(() ->
            {
                System.out.println("第二个子线程" + Thread.currentThread().getName() + "正在执行");
                countDownLatch.countDown();
                System.out.println("第二个子线程" + Thread.currentThread().getName() + "执行完毕");
            }).start();
            //使主线程进入等待状态
            countDownLatch.await();
            System.out.println("子线程执行完毕,主线程开始执行");
    
        }
    }
    复制代码

    执行结果

    CyclicBarrier

    CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 

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

    CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierDemo  {
       static  CyclicBarrier cyclicBarrier=new CyclicBarrier(7, ()-> {
           System.out.println("召唤神龙!");
       });
    
        public static void main(String[] args){
    
            for (int i = 1; i <=7 ; i++) {
                final int count=i;
                new Thread(()->{
                    try {
                        System.out.println(Thread.currentThread().getName()+":获得第"+count+"龙珠");
                        cyclicBarrier.await();
                        System.out.println(Thread.currentThread().getName()+"开始召唤神龙");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
    
        }
    }
    复制代码

    执行结果

     Semaphore

    Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.Semaphore;
    
    public class SemaphoreDemo {
        public static void main(String[] args) {
            //代表现在一共只有三个资源
            Semaphore semaphore=new Semaphore(3);
            //创建十条线程进行资源抢夺
            for ( int i = 0; i <10 ; i++) {
                final int count =i;
                new Thread(()->{
                    try {
                        semaphore.acquire();//请求资源
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第"+(count+1)+"条线程抢到了资源");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第"+(count+1)+"条线程释放资源");
                    //释放资源
                    semaphore.release();
                }).start();
            }
        }
    }
    复制代码

    执行结果

     Exchanger

    用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据

    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.Exchanger;
    
    public class ExchangerDemo {
        public static void main(String[] args) {
            Exchanger exchanger = new Exchanger();
            new Thread(() -> {
                for (int i = 1; i < 10; i++) {
                    try {
                        Thread.sleep(800);
                        int data=i;
                        System.out.println(i+"	"+Thread.currentThread().getName()+"交换前:"+data);
                        data = (int)exchanger.exchange(data);
                        System.out.println(i+"	"+Thread.currentThread().getName()+"交换后:"+data);
                      
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
            new Thread(() -> {
                int count=0;
                while (true) {
                    ++count;
                    try {
                        Thread.sleep(800);
                        int data = 0;
                        System.out.println(count+"	"+Thread.currentThread().getName() + "交换前:" + data);
                        data = (int) exchanger.exchange(data);
                        System.out.println(count+"	"+Thread.currentThread().getName() + "交换后:" + data);
    
    
                    } catch (Exception e) {
                    e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    复制代码

    执行结果

     线程交换的线程数需要是二的倍数,要不然会出现线程阻塞的状态,也可以通过设置响应时间,一旦在规定时间内没有完成数据的交互,那么就抛出异常

    线程池

    Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
    都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
    还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

    ThreadPoolExecutor

    Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,那么它的底层原理是怎样实现的呢,这篇就来介绍下ThreadPoolExecutor线程池的运行过程。

    • corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
    • maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
    • keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
    • unit: 参数keepAliveTime的时间单位,有7种取值

    newCachedThreadPool

    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class CachedThreadPoolDemo {
        public static void main(String[] args) {
            //创建一个可缓存线程如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
            //线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
            ExecutorService  executorService= Executors.newCachedThreadPool();
            for (int i = 0; i <100 ; i++) {
                final int count=i;
                executorService.execute(()->{
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                });
    
            }
        }
    }
    复制代码

    newFixedThreadPool

    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
    复制代码
    package com.yjc.juc;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class FixedThreadPoolDemo {
        public static void main(String[] args) {
            //创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
            ExecutorService executorService= Executors.newFixedThreadPool(5);
            for (int i = 0; i <100 ; i++) {
                final int count=i;
                executorService.execute(()->{
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                });
    
            }
        }
    }
    复制代码

     newScheduledThreadPool

    复制代码
    package com.yjc.juc;
    
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledThreadPoolDemo {
        public static void main(String[] args) {
            //创建一个定长线程池,支持定时及周期性任务执行
            ScheduledExecutorService executorService= Executors.newScheduledThreadPool(5);
            for (int i = 0; i <100 ; i++) {
                final int count=i;
                executorService.schedule(()->{
                    System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                    //延迟三秒开始执行
                },3, TimeUnit.SECONDS);
    
            }
        }
    }
    复制代码

    延迟三秒才开始执行

    newSingleThreadExecutor

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

    复制代码
    package com.yjc.juc;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SingleThreadExecutorDemo {
        public static void main(String[] args) {
            //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
            ExecutorService executorService= Executors.newSingleThreadExecutor();
            for (int i = 0; i <100 ; i++) {
                final int count=i;
                executorService.execute(()->{
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
    
                    }
                    System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                });
    
            }
        }}
    复制代码

    线程池原理剖析

    提交一个任务到线程池中,线程池的处理流程如下:

    1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

    2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

    3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

    合理配置线程池

    CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

    IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

  • 相关阅读:
    选择 冒泡 快速 插入排序
    类方法
    Java--静态区域块
    打印字母如何显示声调
    navicat 创建的表,username字段不能接受中文名字。
    C++primer plus第六版课后编程题答案10.8(来个高手教教我)
    C++primer plus第六版课后编程题答案10.7
    C++primer plus第六版课后编程题答案10.6
    错误 1 error C2143: 语法错误 : 缺少“;”(在“using”的前面)
    C++primer plus第六版课后编程题答案10.5
  • 原文地址:https://www.cnblogs.com/rzbwyj/p/12525686.html
Copyright © 2020-2023  润新知