Java 并发编程 - 线程池 ThreadPool

1、New Thread 弊端

  • 每次 new Thread 新建对象,性能差。
  • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
  • 缺少更多功能,如更多执行、定期执行、线程中断。

2、线程池的好处

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳。

  • 可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。

  • 提供定时执行、定期执行、单线程、并发数控制等功能。

3、线程池 - ThreadPoolExecutor

在这里插入图片描述
在这里插入图片描述

3.1 核心参数

  • corePoolSize:核心线程数量。
  • maximumPoolSize: 线程最大线程数。
  • workQueue: 阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
  • keepAliveTime: 线程没有任务执行时,最多保持多久时间终止。
  • unit: keepAliveTime 的时间单位。
  • threadFactory: 线程工厂,用来创建线程。
  • rejectHandler: 当拒绝处理任务时的策略。
     

3.2 核心方法

  • execute(): 提交任务,交给线程池执行。
  • submit(): 提交任务,能够返回执行结果 execute+Future.
  • shutdown(): 关闭线程池,等待任务都执行完。
  • shutdownNow(): 关闭线程池,不等待任务执行完。
     

3.3 监控方法

  • getTaskCount(): 线程池已执行和未执行的任务总数。
  • getCompletedTaskCount(): 已完成的任务数量。
  • getPoolSize(): 线程池当前的线程数量。
  • getActiveCount(): 当前线程池中正在执行任务的线程数量。
     
package com.icao;

import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;

/**
 * @author
 * @title: NewCachedThreadPoolExample
 * @description: TODO
 * @date 2020/4/16 9:30
 */
@Slf4j
public class NewCachedThreadPoolExample {
    
    

    public static Integer clientCount = 5000;

    public static Integer threadCount = 200;

    public static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws Exception {
    
    

        // 声明 newCachedThreadPool 线程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
        final Semaphore semaphore = new Semaphore(threadCount);
        final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
        for ( int i = 0; i<clientCount; i++) {
    
    
            newCachedThreadPool.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    try {
    
     
                        semaphore.acquire();
                        add();
                        /** 线程监控 log - start**/
                       // 当前线程池中正在执行任务的线程数量
                        int activeCount =
                               ((ThreadPoolExecutor)newCachedThreadPool).getActiveCount();
                        log.info("ActiveCount={}",activeCount);
                        // 线程池已执行和未执行的任务总数
                        long taskCount = 
                               ((ThreadPoolExecutor)newCachedThreadPool).getTaskCount();
                        log.info("taskCount={}",taskCount);
                        /** 线程监控 log - end**/
                        semaphore.release();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                        log.error("exception", e);
                    }
                    // //将计数值减1
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        newCachedThreadPool.shutdown();
        log.info("list.size() = {}",list.size());
        // 线程池当前的线程数量
        int poolSize = 
               ((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
        log.info("poolSize={}",poolSize);
        // 已完成的任务数量。
        long completedTaskCount =
                ((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
        log.info("completedTaskCount={}",completedTaskCount);
    }


    private static void add() {
    
    
      list.add(0) ;
    }

}

3.4 Executors 提供四种线程池

  • Executors.newCachedThreadPool
  • Executors.newFixedThreadPool
  • Executors.newScheduledThreadPool
  • Executors.newSingleThreadExecutor

3.4.1 newCachedThreadPool

 
Executors.newCachedThreadPool :
 
       创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
 

newCachedThreadPool 线程池特点是:
 
1、工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),?这样可灵活的往线程池中添加线程。
 
2、如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
 
3、在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

代码模拟高并发:

package com.icao;

import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;

/**
 * @author 
 * @title: NewCachedThreadPoolExample
 * @description: TODO
 * @date 2020/4/16 9:30
 */
@Slf4j
public class NewCachedThreadPoolExample {
    
    

    public static Integer clientCount = 5000;

    public static Integer threadCount = 200;

    public static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws Exception {
    
    
        long startMili=System.currentTimeMillis();// 当前时间对应的毫秒数
        log.info("/**开始 "+startMili);
        // 声明 newCachedThreadPool 线程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
        final Semaphore semaphore = new Semaphore(threadCount);
        // countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
        // 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
        final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
        for ( int i = 0; i<clientCount; i++) {
    
    
            newCachedThreadPool.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    try {
    
    
                        //   方法 acquire( int permits ) 参数作用,及动态添加 permits 许可数量  
                        //  acquire( int permits ) 中的参数可以这么理解, new Semaphore(6) 表示初始化了 6个通路, semaphore.acquire(2) 表示每次线程进入将会占用2个通路,semaphore.release(2) 运行时表示归还2个通路。没有通路,则线程就无法进入代码块。
                        //  而semaphore.acquire() +  semaphore.release()  在运行的时候,其实和 semaphore.acquire(1) + semaphore.release(1)  效果是一样的。  
                        semaphore.acquire();
                        add();
                        log.info("CurrentThreadId()={},list.size()={}",
                                Thread.currentThread().getId(),list.size());
                        semaphore.release();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                        log.error("exception", e);
                    }
                    // //将计数值减1
                    countDownLatch.countDown();
                }
            });
        }
        // 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
        countDownLatch.await();

        newCachedThreadPool.shutdown();
        // 这里使用countDownLatch 是为了保证最后执行 log.info("list.size() = {}",list.size());
        log.info("list.size() = {}",list.size());
        //这里加入需要测试的代码
        long endMili=System.currentTimeMillis();//结束时间
        log.info("/**结束 s"+endMili);
        log.info("/**总耗时为:"+(endMili-startMili)+"毫秒");
        // 线程池当前的线程数量
        int poolSize =
               ((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
        log.info("poolSize={}",poolSize);
        // 已完成的任务数量。
        long completedTaskCount =
               ((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
        log.info("completedTaskCount={}",completedTaskCount);
    }

    private static void add() {
    
    
      list.add(0) ;
    }
}

3.4.2 newFixedThreadPool

 
Executors.newFixedThreadPool :
 
       创建固定大小(定长)的线程池,可控制线程最大并发数。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,超出的线程会在队列中等待。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
 

3.4.3 newScheduledThreadPool

 
Executors.newScheduledThreadPool :
 
       创建一个定长线程池,支持定时及周期性任务执行。
 

3.4.4 newSingleThreadPool

 
Executors.newSingleThreadExecutor :
 
       创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它

猜你喜欢

转载自blog.csdn.net/weixin_41922349/article/details/105553663