精华内容
下载资源
问答
  • 目前的工作场景是: 在一个项目中需要调用外部接口, 此接口一次只能处理8个请求,多于8个请求过来,nginx会为了保护接口直接踢回请求(返回500null错误),而在本项目中使用了消息队列机制,所以有可能会一次从...

    目前的工作场景是:
    在一个项目中需要调用外部接口,

    此接口一次只能处理8个请求,多于8个请求过来,nginx会为了保护接口直接踢回请求(返回500null错误),而在本项目中使用了消息队列机制,所以有可能会一次从消息队列中消费多条数据,这时候就会有个别请求还没有调用外部接口直接返回了500错误。

    这时候就需要考虑对项目中调用接口的方法进行核心线程控制,这就涉及到hystrix的核心线程数概念。
     

    编写代码模拟外部接口

    这是一个service方法

    @HystrixCommand(commandKey = "testCoreSizeCommand",groupKey = "testGroup",fallbackMethod = "TimeOutFallBack",
        threadPoolProperties = {
                @HystrixProperty(name = "coreSize",value = "2"),
                @HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize",value="true"),
                @HystrixProperty(name = "maximumSize",value="2"),
                @HystrixProperty(name = "maxQueueSize",value="2")
        },
        commandProperties = {
                @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "16000" )
        })
        @Override
        public String coreSizeTest() throws InterruptedException {
    
            Thread.sleep(5000);
            
            System.out.println("进来一次");
            
            return "coreSizeTest finished";
        }
        
        public String TimeOutFallBack(){
            
            return "降级sorry, the request is timeout";
        }
    

    为了更直观的感受,每个请求会sleep 5秒之后返回。

    编写一个controller,调用service:

    @RequestMapping(value="test_coresize",method=RequestMethod.GET)
        public String getHystrixSizeTest() throws InterruptedException{
            
            String test = hystrixService.coreSizeTest();
            
            return test;
        }
    

    这样通过发送请求访问localhost:8080/test_coresize就可以对接口进行调用。

    三个线程同时访问接口

    public class MyThred extends Thread{
    
        @Override
        public void run() {
    
            RestTemplate restTemplate = new RestTemplate();
    
            HttpHeaders headers = new HttpHeaders();
    
            headers.add("Content-Type", "application/json; charset=UTF-8");
            headers.add("Accept", "*/*");
    
            HttpEntity<String> requestEntity = new HttpEntity<>("", headers);
    
            ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:8080/test_coresize", HttpMethod.GET, requestEntity,String.class);
    
            String body = exchange.getBody();
            
            System.out.println("请求结果:"+body);
            
        }
    }
    
    public static void main(String[] args) {
            
            MyThred myThred1 = new MyThred();
            MyThred myThred2 = new MyThred();
            MyThred myThred3 = new MyThred();
            myThred1.start();
            myThred2.start();
            myThred3.start();
        }
    

    测试过程中各种情况分析

    1. 设置coresize=2,maximumSize=2,未定义降级方法

    coresize即核心线程数,即可以同时接受的请求数,maximumSize最大线程数,即超过核心线程数之后,多余的线程会处于空闲状态,等核心线程使用它。开启maximumSize需要设置allowMaximumSizeToDivergeFromCoreSize为true。

    这里我设置两个属性都是2,即一次只能接收两个请求,我最初的想法是既然只能接受两个请求,那我发送三个请求,那是不是应该第三个请求等待2个请求处理完了之后再进行处理。一次请求需要5秒时间,同时可以处理两个请求,那么三个请求应该是10s后全部返回给我。

    但是现实很快就给了我狠狠的一巴掌,两个请求成功,第三个请求返回错误500 null。原来:超过了最大线程数之后的请求会被hystrix降级处理,即调用hystrxi中的fallback属性指定的降级方法。如果没有指定降级方法,则会默认返回null。这就是为什么我会有一个请求返回了500的错误。

    2. 设置coresize=2,maximumSize=2,定义降级方法:返回信息降级sorry, the request is timeout

    在定义了降级方法之后,超过最大线程的请求会调用降级方法,这时候就没有出现报错,而是打印了降级方法返回的信息。

    3. 设置coresize=2,maximumSize=4,未定义降级方法

    由上面的例子我们知道,线程池内核心线程数目都在忙碌,再有新的请求到达时,线程池容量可以被扩充为到最大数量,等到线程池空闲后,多于核心数量的线程还会被回收。

    那我设置了maximumSize=4,这时候有3个请求过来的时候,线程池会被扩大到最大数量,最大容量数大于请求数,不会调用降级方法。这时候比较疑惑的点是三个请求一起处理,还是先处理两个,再处理第三个呢。

    从测试结果上看:三个请求是一起返回过来的。那这就说明:请求数小于最大线程数,却大于核心线程数的时候,会一起处理所有的请求,当所有请求处理完毕的时候,会将多余核心数量的线程释放。

    4. 设置coresize=2,maxQueueSize=2,maximumSize=4,未定义降级方法

    maxQueueSize指的是作业队列的最大值,核心线程池内的线程都在忙碌时,会将作业暂时存放在此队列内,但超出此队列的请求依然会被拒绝。默认值为 -1,设置为此值时,队列会使用 SynchronousQueue,此时其 size 为0,Hystrix 不会向队列内存放作业。即默认hystrix是不会使用队列的。

    那我现在配置了maxQueueSize=2,那么我发送三个请求的时候,就应该是有2个请求在处理,有一个请求在作业队列中等待处理。最后请求返回的时间也应该不是同步的。果然,有两个请求先返回回来,还有一个请求在5秒后返回。

    测试结果总结

    • 如果请求量少,达不到 coreSize,通常会使用核心线程来执行任务。
    • 如果设置了 maxQueueSize,当请求数超过了 coreSize, 通常会把请求放到 queue 里,待核心线程有空闲时消费。
    • 如果 queue 长度无法存储请求,则会创建新线程执行直到达到 maximumSize 最大线程数,多出核心线程数的线程会在空闲时回收。

    所以正确的配置方式是根据显示场景需要进行设置:coresize<maxQueueSize<maximumSize

    需要注意的是threadPoolProperties还有两个属性:

    • queueSizeRejectionThreshold:由于 maxQueueSize 值在线程池被创建后就固定了大小,如果需要动态修改队列长度的话可以设置此值,即使队列未满,队列内作业达到此值时同样会拒绝请求。此值默认是 5,所以有时候只设置了 maxQueueSize 也不会起作用。
    • keepAliveTimeMinutes:由上面的 maximumSize,我们知道,线程池内核心线程数目都在忙碌,再有新的请求到达时,线程池容量可以被扩充为到最大数量,等到线程池空闲后,多于核心数量的线程还会被回收,此值指定了线程被回收前的存活时间,默认为 2,即两分钟。

    在实际的使用过程中,还应该考虑最大超时时间timeoutInMilliseconds与keepAliveTimeMinutes属性的配置,一般线程被回收前的存活时间应该小于最大超时时间,即在请求时间超出超时时间之前,线程应该都处于存活,并处理完所有的请求。
     

     

    展开全文
  • java线程池介绍及简单使用举例

    万次阅读 2017-05-07 21:14:31
    简述了创建线程池的几种方法以及类的关系,并举例介绍了两种线程池的创建方法及使用场景

    多线程虽然能够提升程序的性能,但其实也是一把双刃剑。"为每一个任务分配一个线程"的问题在于资源管理的复杂性。当我们需要频繁的创建多个线程进行耗时操作时,每次通过new Thread来创建并不是一种好的办法。new Thread 新建和销毁对象的性能较差,线程缺乏统一的管理,而且可能出现无限制的创建线程。

    每当看到这种形式的代码时:new Thread(runnable).start()

          并且你希望获得一种更灵活的执行策略,请考虑使用Executor来替代Thread。(参考《Java并发编程实战》)

          因此,我们可以通过使用线程池来管理线程。线程池是指管理一组同构工作线程的资源池。其原理简单解释就是会创建多个线程并进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行。

          java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分,Executor接口如下:

        public interface Executor {
             void execute(Runnable command);
        }
         虽然Executor是个简单的接口,但它缺位灵活且强大的异步任务执行框架提供了基础。

         线程池都实现了ExecutorService接口,该接口定义了线程池需要实现的方法:

    public interface ExecutorService extends Executor {
        void shutdown();
        List<Runnable> shutdownNow();
        boolean isShutdown();
        boolean isTerminated();
        boolean awaitTermination(long timeout, Ti
          meUnit unit)
            throws InterruptedException;
        <T> Future<T> submit(Callable<T> task);
        //.......(代码省略其他用于任务提交的便利方法)
    }
       ExecutorService接口的实现有,ThreadPoolExecutor和ScheduledThreadPoolExcecutor。

       ThreadPoolExecutor继承自抽象类AbstractExecutorService,该抽象类实现了ExecutorService接口。ThreadPoolExecutor也是我们运用最多的线程池。

        ScheduledThreadPoolExcecutor 继承了ThreadPoolExecutor 并实现了ScheduledExecutorService接口,ScheduledExecutorService接口继承自ExecutorService接口。ScheduledThreadPoolExcecutor用于周期性的执行任务,和Timer类类似。但Timer存在一些缺陷,因此应该考虑使用ScheduledThreadPoolExcecutor来代替它(Timer支持基于绝对时间而不是相对时间的调度机制,因此任务的执行对系统时钟变化很敏感,而ScheduledThreadPoolExcecutor只支持基于相对时间的调度)。

        我们可以通过ThreadPoolExecutor的构造函数来实例化一个对象,但由于创建参数相对复杂,通常选择Exectors工厂类的静态方法来创建一个线程池:

        newFixedThreadPool。将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程池,直到达到线程池的最大数量,这时线程池的规模将不再变化。

        newCachedThreadPool。将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。

        newSingleThreadPool。是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool能确保依照任务在队列中的顺序来串行执行(例如FIFO、LIFO、优先级)。

        newScheduledThreadPool。创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

        newFixedThreadPool和newCachedThreadPool 返回通用的ThreadPoolExecutor实例,这些实例可以直接用来构造专门用途的executor。


       当默认的创建线程池策略无法满足要求时,那么可以通过ThreadPoolExecutor构造函数来实例化一个对象,根据自己的需求来实现定制,ThreadPoolExecutor最常见的构造函数形式如下:

        

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) 
    参数说明:

    corePoolSize:线程池中所保存的核心线程数。线程池启动后默认是空的,只有任务来临时才会创建线程以处理请求。

    maximumPoolSize:线程池允许创建的最大线程数。它与corePoolSize的作用是调整“线程池中实际运行的线程的数量”。当新任务提交给线程池时,如果线程池中运行的线程数量小于corePoolSize,则创建新线程来执行任务;如果此时,线程池中运行的线程数量大于corePoolSize,但小于maximumPoolSize,则仅当阻塞队列满时才创建新线程。如果corePoolSize与maximumPoolSize相同,则创建固定大小的线程池。如果将maximumPoolSize设置为基本的无界值(如Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。

    keepAliveTime:当前线程池线程总数达到核心线程数时,终止多余的空闲线程的时间。

    Unit:keepAliveTime参数的时间单位,可选值有毫秒、秒、分等。

    workQueue:任务队列。如果当前线程池达到核心线程数量corePoolSize后,且当前所有线程都处于活动状态时,则将新加入的任务放到此队列中。

    threadFactory:线程工厂,让用户可以定制线程的创建过程,一般不需要设置。

    Handler:拒绝策略,当线程池和任务队列workQueue都满了的情况下,对新加的任务采取的处理策略。

    下面通过两个简单的例子来说明线程池的简单使用:

    1.通过Executors.newFixedThreadPool(int)来创建一个固定数量的线程池,代码如下:

    public class MyExecutorDemo {
        //执行的任务数量
        private static int MAX = 10;
    
        public static void main(String args[]){
            try {
                fixedThreadPool(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        private static void fixedThreadPool (int coreSize)
                throws InterruptedException,ExecutionException {
            //创建线程池
            ExecutorService exec = Executors.newFixedThreadPool(coreSize);
            for(int i = 0; i < MAX; i++){
                //提交任务
                Future<Integer> task = exec.submit(new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        System.out.println("执行线程" + Thread.currentThread().getName());
                        return fibc(20);
                    }
                });
                //获取执行结果
                System.out.println("第"+i+"次计算,结果为"+task.get());
            }
        }
        //模拟耗时操作,定义一个斐波那契数列
        private static int fibc(int num){
            if (num == 0){
                return 0;
            }
            if (num == 1){
                return  1;
            }
            return fibc(num-1)+fibc(num-2);
        }
    
    }
    在上述代码中,通过fixedThreadPool启动了含有4个线程的线程池,我们看一下该方法的构造函数,

    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    第一个和第二个参数时相同的值,则创建固定大小的线程池,最后的参数为无界队列,因此该线程池可容纳无限个任务。创建成功后,向线程池中通过submit提交了10个Callable任务,每个任务计算前20个的斐波那契数列。通过结果可以看出,线程池中有4个线程交替的执行任务,执行运算结果如下:

    执行线程pool-1-thread-1
    第0次计算,结果为6765
    执行线程pool-1-thread-2
    第1次计算,结果为6765
    执行线程pool-1-thread-3
    第2次计算,结果为6765
    执行线程pool-1-thread-4
    第3次计算,结果为6765
    执行线程pool-1-thread-1
    第4次计算,结果为6765
    执行线程pool-1-thread-2
    第5次计算,结果为6765
    执行线程pool-1-thread-3
    第6次计算,结果为6765
    执行线程pool-1-thread-4
    第7次计算,结果为6765
    执行线程pool-1-thread-1
    第8次计算,结果为6765
    执行线程pool-1-thread-2
    第9次计算,结果为6765

    newCachedThreadPool

    2.通过Executors.newCacheThreadPool()创建带缓存的线程池

        有时,我们需要任务尽可能快的被执行,这需要线程池中的线程足够多,也就是说需要用空间来换时间,创建的线程越多,占用的内存消耗也越大。但由于其并发量也越大,因此执行的速度也越快。考虑这样一种场景,在新提交了一个任务后,由于当前没有空闲线程可执行,因此需要马上创建一个线程来执行该任务。这种场景可通过该方法来实现。代码如下:

    private static void newCachedThreadPool () throws ExecutionException,
                InterruptedException{
    
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < MAX; i++){
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("执行线程为:"+Thread.currentThread().getName()+
                                ",结果为:"+fibc(30));
                    }
                });
            }
        }

    代码中,通过newFixedThreadPool创建了一个线程池,并每次提交一个任务,来计算前30个的斐波那契数列,运行结果如下:

    执行线程为:pool-1-thread-2,结果为:832040
    执行线程为:pool-1-thread-1,结果为:832040
    执行线程为:pool-1-thread-3,结果为:832040
    执行线程为:pool-1-thread-4,结果为:832040
    执行线程为:pool-1-thread-1,结果为:832040
    执行线程为:pool-1-thread-5,结果为:832040
    执行线程为:pool-1-thread-2,结果为:832040
    执行线程为:pool-1-thread-6,结果为:832040
    执行线程为:pool-1-thread-1,结果为:832040
    执行线程为:pool-1-thread-5,结果为:832040

    为了保证执行效率,每次提交任务,线程池都会创建一个新线程来执行任务,但前提是此时没有空闲线程才创建新线程,但有空闲线程时,则会使用空闲的线程来执行任务,如结果中所示,执行前4个任务时,线程池为每个任务都创建了一个线程,当执行到第5个任务时,此时第一个线程已经执行完任务,并处于空闲状态,那么第5个任务就被执行在第1个线程中了。

    (参考书籍《Java并发编程实战》、《Android开发进阶从小工到专家》)






         



    展开全文
  • 1、CountDownLatch:一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。...3、使用场景举例: 年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴

    1、CountDownLatch:一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

    2、ThreadPoolExecutor/ExecutorService:线程池,使用线程池可以复用线程,降低频繁创建线程造成的性能消耗,同时对线程的创建、启动、停止、销毁等操作更简便。

    3、使用场景举例:
    年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴前往目的地。
    在这个案例中,公司作为主线程,员工作为子线程。

    4、代码示例:

    package com.test.thread;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /** 
     * @author  javaloveiphone
     * @date 创建时间:2017年1月25日 上午10:59:11 
     * @Description: 
     */
    public class Company {
        public static void main(String[] args) throws InterruptedException {
    
            //员工数量
            int count = 5;
            //创建计数器
            //构造参数传入的数量值代表的是latch.countDown()调用的次数
            CountDownLatch latch = new CountDownLatch(count);
    
            //创建线程池,可以通过以下方式创建
            //ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(count));
            ExecutorService threadPool =  Executors.newFixedThreadPool(count);
    
            System.out.println("公司发送通知,每一位员工在周六早上8点到公司大门口集合");
            for(int i =0;i<count ;i++){
                //将子线程添加进线程池执行
                Thread.sleep(10);
                threadPool.execute(new Employee(latch,i+1));
            }
            try {
                //阻塞当前线程,直到所有员工到达公司大门口之后才执行
                latch.await();
                // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
                //latch.await(long timeout, TimeUnit unit)
                System.out.println("所有员工已经到达公司大门口,大巴车发动,前往活动目的地。");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                //最后关闭线程池,但执行以前提交的任务,不接受新任务
                threadPool.shutdown();
                //关闭线程池,停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
                //threadPool.shutdownNow();
            }
        }
    }
    
    //分布式工作线程
    class Employee implements Runnable{
    
        private CountDownLatch latch;
        private int employeeIndex;
    
        public Employee(CountDownLatch latch,int employeeIndex){
            this.latch = latch;
            this.employeeIndex = employeeIndex;
        }
    
        @Override
        public void run() {
            try {
                System.out.println("员工:"+employeeIndex+",正在前往公司大门口集合...");
                Thread.sleep(10);
                System.out.println("员工:"+employeeIndex+",已到达。");
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //当前计算工作已结束,计数器减一
                latch.countDown();  
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行coutDown()之后,继续执行自己的工作,不受主线程的影响
                System.out.println("员工:"+employeeIndex+",吃饭、喝水、拍照。");
            }
        }
    }
    

    打印输出结果如下:

    公司发送通知,每一位员工在周六早上8点到公司大门口集合
    员工:1,正在前往公司大门口集合...
    员工:1,已到达。
    员工:2,正在前往公司大门口集合...
    员工:2,已到达。
    员工:1,吃饭、喝水、拍照。
    员工:3,正在前往公司大门口集合...
    员工:2,吃饭、喝水、拍照。
    员工:3,已到达。
    员工:4,正在前往公司大门口集合...
    员工:3,吃饭、喝水、拍照。
    员工:4,已到达。
    员工:5,正在前往公司大门口集合...
    员工:4,吃饭、喝水、拍照。
    员工:5,已到达。
    所有员工已经到达公司大门口,大巴车发动,前往活动目的地。
    员工:5,吃饭、喝水、拍照。

    注意:
    每一个员工到达之后,执行countDown()方法,直到所有员工到达之后,计数器为0,主线程才会继续执行。

    但子线程执行了countDown()方法,之后会继续自己的工作,比如上面的【吃饭、喝水、拍照】,是不受主线程是否阻塞、其它线程是否已经执行countDown()方法的影响的。

    5、CountDownLatch与CyclicBarrier的对比可以看: java多线程CyclicBarrier使用示例,让线程起步走

    6、参考:
    http://www.importnew.com/15731.html
    http://www.cnblogs.com/yezhenhan/archive/2012/01/07/2315652.html

    展开全文
  • 线程池是指在进程开始时创建一定数量(有上限)的线程,并放入池中,需要使用时,就从池中取出来一个可用线程,用完后,不是销毁,而是返回池中。如果线程用完了,就需要等待有空闲的线程后,才能使用。 java在JDK1.5...

    1.什么是线程池?

    线程池是指在进程开始时创建一定数量(有上限)的线程,并放入池中,需要使用时,就从池中取出来一个可用线程,用完后,不是销毁,而是返回池中。如果线程用完了,就需要等待有空闲的线程后,才能使用。

    java在JDK1.5后就引入了线程池。所以不需要我们自己实现一个线程池。
    举例说明:

    没有使用线程池的时候,假设我们要看一本书“java编程思想”,是直接到网上买一本书,买来后,看完就丢弃(销毁)。
    使用多线程,这次我们不是直接到网上买一本书,都是通过到图书馆,借"java编程思想这本书",我看完后,归回到图书馆,这时候其他的人,还可以继续重复阅读(线程的重复利用)。
     

    2.实战

    2.1通过线程池代码创建线程

     public void two() throws Exception{
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int count=0;
                    for (int i = 0; i < 5; i++) {
                        Thread.sleep(1200);
                        count++;
                    }
                    return count;
                }
            };
    
            ExecutorService e= Executors.newFixedThreadPool(10);
            Future<Integer> f1=e.submit(callable);
            Integer result = f1.get();
            System.out.println("获取多线程的值:"+result);
        }
    

    通过上述代码,我们可以知道实现线程池涉及到ExecutorService和Executors。下面我们来一个个进行源码分析

    2.1.Executors源码分析

    在idea中,把光标放到Executors上,按住鼠标左键+ctrl进入Executors类。输入alt+7查看该类下的所有方法。

     

    • newFixedThreadPool(int nThreads) 创建一个重用固定数量线程的线程池,如果在所有线程都处于活动状态时提交了额外的任务,他们将在队列中等待,直到线程可用为止。
    • newWorkStealingPool(int parallelism) jdk1.8后引入的,它是新的线程池类ForkJoinPool的扩展,能够合理的使用CPU,进行并发运行任务。可以了解为,工作量一样,A,B同时开发,谁开发的快,谁就多做一些,能者多劳(不提倡工作中这样,只是为了通过实例更好的理解这个概念)。
    • newSingleThreadExecutor() 创建一个单例的线程池,也就是说池中就一个线程。通过这个线程来处理所有的任务,如果发现这个这个线程因为失败而关闭,不要慌,会有一个线程来取代他,保证任务能正常的运转
    • newCachedThreadPool() 创建一个可缓存线程池,如果没有线程可用,发现60s内有线程不工作,会创建一个线程的线程来取代他,再放入池中。 可以理解为一个公司为了保证公司的正常运转,会请20个人,但是,上班期间发现隔壁小王在偷懒,那不好意思,针对这种偷懒的人,公司表示不欢迎,直接开除,重新招一个人,保证公司的人员固定能正常运营。
    • newSingleThreadScheduledExecutor() 创建一个单线程的线程池,此线程池的的线程可以定时周期性的运行任务。注意坑点:使用这种方法,如果出现异常,会导致无法正常的运行任务。所以,个人建议,使用这种方式的时候,run方法里面的代码可以加上异常处理逻辑。
    • newScheduledThreadPool(int corePoolSize) 创建一个固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。

    newFixedThreadPool

    创建一个重用固定数量线程的线程池,如果在所有线程都处于活动状态时提交了额外的任务,他们将在队列中等待,直到线程可用为止。

    package com.cxyxs.thread.four;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/20 11:12
     * Modified By:
     */
    public class NewFixedThreadPool {
        public static void main(String[] args) {
            //System.out.println(Runtime.getRuntime().availableProcessors());
            Runnable run = new Runnable() {
    
                @Override
                public void run() {
                    try {
                        System.out.println("程序猿学社:"+new Date());
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
    
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            for (int i = 0; i < 10; i++) {
                executorService.execute(run);
            }
        }
    }
    

    为了更好的看出效果,我特意把每个任务都延迟了3秒钟,模拟真实的场景,各位社友,觉得他的输出结果应该是怎么样的?

     

    通过控制台,我们可以发现,每次只处理两个任务,而其他的任务处在排队状态,依次处理。
    为什么只处理两个任务?有没有社友想到为什么?
    这是因为社长设置了线程池中的数据大小为2。讲到这里有引发一个疑问。
    线程池里面的数据量可以随便设置吗?
    不能随便设置,不同的开发人员设置的标准不一样,个人是CPU核数的2倍。

    例如,你单个cpu,同一时间只有一个通道,可以运行任务。就算你设置100个线程,实际上也是交替运行的。
    不要社长这样说,就觉得CPU是单核,就觉得多线程没有一点用,实际上,在网络通讯过程中,解析包,处理包,就是通过多线程,把解析和处理的逻辑切割开来。实现解耦。这样有一个好处,就算你处理业务逻辑的线程很慢,也不会影响我解析包的线程正常运转。
    newWorkStealingPool(int parallelism)
    jdk1.8后引入的,它是新的线程池类ForkJoinPool的扩展,能够合理的使用CPU,进行并发运行任务。
     

    package com.cxyxs.thread.four;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/20 14:15
     * Modified By:
     */
    public class WorkStealingPool {
        public static void main(String[] args) throws  Exception{
            //再测试之前,我们应该了解我们电脑的cpu核数,我的电脑是4核
            System.out.println(Runtime.getRuntime().availableProcessors());
    
            ExecutorService executorService = Executors.newWorkStealingPool();
    
            for (int i = 0; i < 10; i++) {
                //产生一个随机数1-3
                int num = (int) (Math.random() * 3 + 1);
    
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //模拟正在业务
                            Thread.sleep(num*1000);
                            System.out.println("线程名:"+Thread.currentThread().getName()+",业务时长:"+num+"秒");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
                executorService.execute(runnable);
            }
            //因为是守护进程,如果不加这句话是无法看到结果的
            System.in.read();
        }
    }
    

    因为newWorkStealingPool是并发运行,既然人工作的效率都有高低,电脑也是一样。已经完成任务的,不可能让他闲着,这样会造成资源的浪费。

    重点下面这句话要理解。打印出来的4,表示我们电脑只能并行4个线程。

    Runtime.getRuntime().availableProcessors()
    • 通过上面打印的线程名,我们可以发现他用的是ForkJoinPool线程池。这是jdk1.7引入的。
    • 数字0-3 表示我们电脑同一时刻在工作(只是针对这块而言,这句话扩大来说是有问题的)。
    • 我们可以发现work1线程跑了3次,说明执行完,自己的任务后,发现其他的小伙子还没有做完,开始继续接任何开始做事了(rabbitMQ里面有一个地方设置条数为1,跟这里的思想类似,意思就是我每次从队列里面取1条,我消费完,再继续取)。
    System.in.read();
    • 因为是守护进程,如果不加这句话是无法看到结果的,后文会详细说明

    newSingleThreadExecutor()

    创建一个单例的线程池,也就是说池中就一个线程。通过这个线程来处理所有的任务,如果发现这个这个线程因为失败而关闭,不要慌,会有一个线程来取代他,保证任务能正常的运转

    package com.cxyxs.thread.four;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/20 14:48
     * Modified By:
     */
    public class NewSingleThreadExecutor {
        public static void main(String[] args) {
            ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 10; i++) {
                final  int index=i;
                singleThreadExecutor.execute(new Runnable() {
    
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        if(index == 5){   //故意搞破坏
                            int flag =index/0;
                        }
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    

    了验证我上面那段话,我特意搞破坏,让i=5的时候,除以0,大家都知道0不能作为分母。

    • 通过图,我们可以发现,之前的线程名一直是XXX-1,到了第五次报错后,就直接抛出异常,重新起了一个新的线程是XXX-2。
    • newSingleThreadExecutor看名称就知道是单例线程池的意思。那就只有一个线程。一次只能运行一个任务,所以他也是顺序执行的。

    newCachedThreadPool()

    创建一个可缓存线程池,如果没有线程可用,发现60s内有线程不工作,会创建一个线程的线程来取代他,再放入池中。 可以理解为一个公司为了保证公司的正常运转,会请20个人,但是,上班期间发现隔壁小王在偷懒,那不好意思,针对这种偷懒的人,公司表示不欢迎,直接开除,重新招一个人,保证公司的人员固定能正常运营。
     

    package com.cxyxs.thread.four;
    
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/20 15:02
     * Modified By:
     */
    public class NewCachedThreadPool {
        public static void main(String[] args) throws  Exception{
            ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) {
               int index = i;
                cachedThreadPool.execute(new Runnable() {
    
                    @Override
                    public void run() {
                        if(index == 0){
                            try {
                                Thread.sleep(8000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("第一次:"+Thread.currentThread().getName()+","+new Date());
                    }
                });
            }
    
            Thread.sleep(7000);
    
            for (int i = 0; i < 10; i++) {
                int index = i;
                cachedThreadPool.execute(new Runnable() {
    
                    @Override
                    public void run() {
                        if(index == 0){
                            try {
                                Thread.sleep(8000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("第二次:"+Thread.currentThread().getName()+","+new Date());
                    }
                });
            }
        }
    }
    

    首先我这个代码看起来很乱。因为自身为了论证结论。所以没有把代码做一个简单的抽取。
    代码设计:通过我的代码,可以发现,第一次循环5次,我把第一条数据设置延迟80s,我还特意在main主线程里面延迟了70s。,第二次,循环10次。为什么这样设计?

    • 就是为了论证到了60秒以后,到底是不是把线程回收了。第一次第一个线程输出后,本身线程数量为9,被回收到了1。
    • 线程的复用,我们可以看图,XXXX-3,出现了2次,说明就是这个线程执行完后,如果这时候有线程过来,其他的线程就可以直接复用这个线程。
       

    newSingleThreadScheduledExecutor()

    创建一个单线程的线程池,此线程池的的线程可以定时周期性的运行任务。注意坑点:使用这种方法,如果出现异常,会导致无法正常的运行任务。所以,个人建议,使用这种方式的时候,run方法里面的代码可以加上异常处理逻辑。这种方式newSingleThreadExecutor类似,只是增加了周期性运行,这里不过多的阐述。

    newScheduledThreadPool(int corePoolSize)

    创建一个固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。
    package com.cxyxs.thread.four;
    
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/20 15:43
     * Modified By:
     */
    public class NewScheduledThreadPool {
        public static void main(String[] args) {
            ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
            executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("开始时间:"+Thread.currentThread().getName()+","+new Date());
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },2000,6000,TimeUnit.MILLISECONDS);
        }
    }
    

    通过截图我们可以发现是6秒一次。

    • 保证period>initialDelay,以period为准。以period的时长为一个周期
    • 如果run方法运行时间大于period,定时任务的周期以run运行时长为一个周期。
    • 详细这个方法的时候,本文就不阐述了,后续会出相关文章。
    展开全文
  • java中的线程池

    2017-12-15 09:55:37
    具体的使用场景举例分析使用线程池的好处降低资源消耗 创建和销毁线程需要消耗资源,线程池可以重复利用已创建的线程,减少资源消耗。提高响应速度 创建线程需要时间,当任务到达时,立即执行,无需等待线程创建...
  • 举例场景:当用户支付成功之后,需要马上通知商家(发短信或者语音提示功能等),然后再处理其他业务,如果按照正常逻辑,同步执行,当通知商家这个步骤响应很慢,则后面逻辑就需要等待它执行完毕后才能继续走下去。...
  • 3、使用场景举例: 年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴前往目的地。 在这个案例中,公司作为主线程,员工作为子线程。 4、代码示例: `package` `c
  • 线程池的介绍 不用线程池存在的问题 反复创建销毁线程开销大 过多的线程占用大量内存 线程池的使用,就类似于计划经济,控制资源总量,复用线程。...线程的使用场景举例: 服务器接收大量请求,使用...
  • 首先举例一个业务场景,也是日常开发中经常会遇到的场景,手机短信验证业务。 短信验证在很多业务场景中都会遇到,比如验证码登录,短信提示余额不足等等。类似这种不影响主线程业务的,在访问量很大的情况下,就...
  • 3、使用场景举例: 年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴前往目的地。 在这个案例中,公司作为主线程,员工作为子线程。 4、代码示例: 1 2 3 4 5
  • 目录业务场景代码结构UserController.javaUser.javaUserThreadHandle.javaUserMapper.javaUserMapper.xmlUserService.javaUserServiceImpl.java运行结果: 业务场景 ...(此处只是举例多线程、线程池
  • 原因:开放寻址法的特点和使用场景是数据量比较少的情况下性能更好;而Hashmap里面存储的数据 问题:为什么会发生OOM? 答:ThreadPool(长生命周期)----》Thread–>ThreadLocalMap—>Entry[]–>Entry—&g
  • ThreadLocal 应用场景举例: SpringSecurity 存储当前登录的用户信息–> SecurityContextHolder Spring 中事务处理,保证多个Service公用一个连接才能保证事务的一致性,会把Connection信息存储在ThreadLocal中 ...
  • 定义:单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。 应用场景: ...应用场景举例
  • Java面试题

    2020-07-08 12:00:26
    Java部分 1、基础篇 ①怎么理解面对对象? ②重载和重写区别? ③什么是字节码?采用字节码文件最大的好处? ...举例几种常见的线程池以及适用场景? 4、网络篇 ①TCP三次握手和四次挥手? ②TCP和
  • 设计模式--单例模式

    2018-02-26 14:06:56
    单例模式特点: 一个类只能有一个实例,并且这个类能自己创建出自己的实例供别人使用。类图:很简单,只有一个类。...单例模式使用场景举例:比如一些资源管理器,数据库连接池,线程池等,方便资源控制和资源共...
  • 单例模式使用场景举例 各种池的对象,比如进程池,线程池,数据库连接池,这些情况只能创建一个实例 工厂模式的工厂对象 负载均衡模型里面的任务分配器 这些实例都有一个共同的特点,那就是通过这些实例可以操作...
  • 设计模式-单例模式

    2019-10-11 22:48:40
    应用场景举例 数据库连接池、线程池、容器、配置类等。 实现 1. 懒加载,非线程安全 这种方式非线程安全,因此多线程情况不能使用。 public class Singleton { private static Single...
  • 二、单例模式的使用场景: (1)资源共享的情况下:避免由于资源操作导致的性能消耗。如上述中的日志文件、应用配置。 (2)控制资源的情况下,方便资源之间的互相通信,如线程池。 三、单例模式的常用环境举例:...
  • Java 单例模式详解

    2020-09-23 21:16:30
    场景 之所以使用单例模式,是因为某些重量级的对象,不需要多个实例,因为占用系统资源过高。例如线程池,数据库连接池。 三 两种经典模式 1.懒汉模式 只有在对象使用时才初始化。 举例代码如下: /** ...
  • 场景:常被用来管理共享的资源,例如数据库连接或者线程池。 具体需求:召唤师峡谷中当英雄第一次访问商店时,创建这个商店,供以后所有的英雄使用。 三、代码实现 单例 商城public class Store { privat.....
  • 多线程锁的启蒙

    2020-12-23 15:47:32
    一:在多线程中用到锁的地方非常...在tomcat的线程池多线程中(我的意思是说针对每次客户请求在你业务的时候使用锁,例如电商项目的超卖超扣 / 项目接口的幂等性) 多线程的通信,这种场景的线程都是跟随主线程(app程序
  • python 的单例模式

    2020-07-29 17:06:47
    今天拿下 python 的单例模式 单例模式,通俗的理解就是单个实例对象的模式,就是说这个类只能有一个实例对象 ...python实现单例模式一般有四种,我们按使用频率较多的两种举例(个人见解) 使用 new (父类重写new继
  • CachedThreadPool(无界线程池,可以进行自动线程回收)、FixedThreadPool(固定大小线程池)和SingleThreadExecutor()(单个后台线程),ScheduledThreadPool(定时,延时线程池)它们均为大多数使用场景预定义了...
  • 以我们常见的商品列表的接口举例,该接口不仅仅是调用了商品中心商品信息接口,还调用了定价服务的用户级别价格接口,然后组合(zip)成为一个商品完整信息返回。 在某些情况下价格...

空空如也

空空如也

1 2
收藏数 27
精华内容 10
关键字:

线程池使用场景举例