线程创建方式及线程池使用原理

线程创建方式及线程池使用原理

Thread类用于操作线程,所有的线程对象都必须是Thread类或其子类的实例

创建线程的四种方式:

  • 继承Thread类

  • 实现Runnable接口

  • 使用Callable和Future

  • 线程池

方法1是把线程和任务合并在了一起

方法2,3是把线程和任务分开了 ,任务在Callable,Runnable中写,任务类脱离了Thread继承体系,更灵活

方法4:使用线程池管理线程,程序员只用关心任务,将其都给线城池执行

1、继承Thread类

步骤:

1)继承Thread类 ​ 2)重写run方法,方法体就是线程要完成的任务 ​ 3)创建Thread子类的实例 ​ 4)调用线程start(),启动线程

class MyThread extends Thread{
  public void run(){}//重写run方法
}
​
public void testThread(s){  
    new MyThread().start();//创建并启动线程
  }
}

简化写法:

// 创建线程对象
Thread t = new Thread() {
 @Override
 public void run() {//重写run方法,覆盖原方法
 // 要执行的任务
 }
};
// 启动线程
t.start();

2、实现Runnable接口

步骤:

1)实现Runable接口,重写run方法

2)创建Runnable实现类的实例

3)将上述实例作为Thread(Runnable)构造器的初始化对象,创建线程

4)启动线程start()

​
class MyRunnable implements Thread{
  public void run(){}//重写run方法
}
​
public void testRunnable(){ 
    Runnable myRunnable = new MyRunnable()
    new Thread().start(myRunnable);//创建并启动线程
  }
}

简化写法:

Runnable runnable = new Runnable() {
 public void run(){
 // 要执行的任务
 }
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start(); 

Java 8 以后可以使用 lambda 精简代码

// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
//Thread t2 = new Thread(() -> log.debug("hello"), "t2");
t2.start();

3、Callable与Future实现

Callable、FutureTask介绍

Callable与Runnable接口区别:

  • Callable提供的线程执行体是Call()

  • call()有返回值

  • call()可以声明抛出异常

线程创建方式及线程池使用原理

Thread的构造方法没有一个传递Callable,都是传递Runnable,没有Callable提供的构造器,不通用,但是要使用Callable实现多线程怎么办,如何适配两没关系的接口,没有一个是多加一层解决不了的,找一个中间人,将两者衔接起来,一个类可以实现多个接口,如果一个类实现了Runnable接口,又与Callable接口挂上关系,传这个类就可以变相的适配了。所以提供了FutureTask

interface RunnableFuture<V>它实现了Runnable接口,其构造方法 FutureTask(Callable<V> callable),实现了两者连接。因此可以作为Thread类的target 这里的设计思想Good

线程创建方式及线程池使用原理

 

FutureTask构造方法

public class FutureTask<V> implements RunnableFuture<V> {
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
}

常用方法:

  • boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

  • V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

  • V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

  • boolean isDone():若Callable任务完成,返回True

  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

使用实例:

步骤:

1)实现Callable接口,重写call方法

2)创建Callable实现类的实例,并用其创建FutrueTask实例

3)使用FutureTask实例创建线程

4)启动线程start()

class MyCallable implements Callable<Integer> {
    public Integer call() throws Exception {
        //要执行的方法
        return XXX;
    }
}
public void testCallable(){ 
    FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
    new Thread(futureTask, "AA").start();
    int result02=futureTask.get();
  }
}

简化写法:

// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
 log.debug("hello");
 return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);

4、线程池

4.1 介绍

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果任务数量超过了最大数量的线程则进入阻塞队列排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

优点:

降低资源消耗:通过重复利用己创建的线程降低线程创建和销毁造成的消耗。 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间

线程池最大优点是把任务的提交和执行解耦,要执行任务的人只需把Task描述清楚,然后提交即可,不用再管内部怎么执行

Executor继承关系:

线程创建方式及线程池使用原理

 

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable command),返回值为void,参数为Runnable类型,用来执行传进去的任务的,任务即一个实现了Runnable接口的类

ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

ThreadPoolExecutor继承了类AbstractExecutorService。

ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

4.2 ThreadPoolExecutor核心类介绍

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,可通过该类自定义线程池

1)构造方法

在ThreadPoolExecutor类中提供了四个构造方法:

线程创建方式及线程池使用原理

/*
上述构造方法前三个都调用了第四个
​
*/
public ThreadPoolExecutor(
    int corePoolSize,//线程池中的常驻核心线程数
    int maximumPoolSize,//线程池能够容纳同时执行的最大线程数,此值必须大于等于1
    long keepAliveTime,//多余的空闲线程的存活时间。
    TimeUnit unit,//keepAliveTime的单位
    BlockingQueue<Runnable> workQueue,//任务队列,被提交但尚未被执行的任务。
    ThreadFactory threadFactory,//生成线程池中工作线程的线程工厂,用于创建线程一般用默认的
    RejectedExecutionHandler handler)//拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数
{...。。略 }

2)参数介绍:

  1. corePoolSize:线程池中的常驻核心线程数

    1. 在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。

    2. 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

    keepAliveTime:多余的空闲线程的存活时间。

    1. 当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止 unit:keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

      TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒

    workQueue:任务队列,被提交但尚未被执行的任务。

    ArrayBlockingQueue:由数组结构组成的有界阻塞队列。(重点) LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。(重点) PriorityBlockingQueue:支持优先级排序的*阻塞队列。 DelayQueue:使用优先级队列实现的延迟*阻塞队列。 SynchronousQueue:不存储元素的阻塞队列,也即单个元素队列(重点) LinkedTransferQueue:由链表结构绒成的*阻塞队列。 LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

    threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

    handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时拒绝处理任务时的策略( maximumPoolSize)。

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

3)常用方法:

execute():实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit():在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果
------------------------------------------------
//关闭线程池
shutdown():不会立即终止线程池,而是要等所有任务(包括正在执行的任务和缓冲队列中的任务)缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
----------------------------------------
//获取与线程池相关属性的方法
getQueue()
getPoolSize()
getActiveCount()
getCompletedTaskCount()
----------------------------
动态调整线程池容量大小
setCorePoolSize():设置核心池大小
setMaximumPoolSize():设置线程池最大能创建的线程数目大小

4.3 线程池实现原理

1)线程池状态

  在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

//当前线程池的状态,用volatile修饰保证线程间的可见性
volatile int runState;
//当创建线程池后,初始时,线程池处于RUNNING状态
static final int RUNNING    = 0;
//如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
static final int SHUTDOWN   = 1;
//如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
static final int STOP       = 2;
//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
static final int TERMINATED = 3;

2)线程池中的线程初始化

 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

  在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

  • prestartCoreThread():初始化一个核心线程;

  • prestartAllCoreThreads():初始化所有核心线程

3)线程池工作原理

 线程创建方式及线程池使用原理

线程创建方式及线程池使用原理

在创建了线程池后,等待提交过来的任务请求。

当调用execute()方法添加一个请求任务时,线程池会做如下判断:

  1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

  2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

  3. 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

  4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

  5. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:

  1. 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

4)任务缓存队列及排队策略

  在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

  workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

  1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

  2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

  3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

5)任务拒绝策略

  当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:(默认)丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,不做任何处理也不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy:丢抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

以上内置拒绝策略均实现了RejectedExecutionHandler接口

4.4 Executors创建线程池

Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口

  • Executors.newScheduledThreadPool() -线程池中设定一个时间参数,比如池子中的请求每2s执行一次

  • Executors.newWorkStealingPool(int) - Java8新增,使用目前机器上可用的处理器作为它的并行级别 重点

  • Executors.newSingleThreadExecutor() 一个任务一个任务执行的场景

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

主要特点如下:

1.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。 2.newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue。 * Executors.newFixedThreadPool(int) 执行长期任务,性能好很多

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

主要特点如下:

1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 2.newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue。

  • Executors.newCachedThreadPool() 适用:执行很多短期异步的小程序或者负载较轻的服务器

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

主要特点如下:

1.创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 2.newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

使用实例:

1)Executor执行Runnable任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class ThreadPoolDemo {
    public static void main(String[] args) {
//线程池可类比银行,每个业务窗口相当于处理线程
​
        // 一池5个处理线程(用池化技术,一定要记得关闭)
//      ExecutorService threadPool = Executors.newFixedThreadPool(5);
​
        // 创建一个只有一个线程的线程池
//      ExecutorService threadPool = Executors.newSingleThreadExecutor();
​
        // 创建一个拥有N个线程的线程池,根据调度创建合适的线程
        ExecutorService threadPool = Executors.newCachedThreadPool();
​
        // 模拟10个用户来办理业务,每个用户就是一个来自外部请求线程
        try {
​
            // 循环十次,模拟业务办理,让5个线程处理这10个请求
            for (int i = 0; i < 10; i++) {
                final int tempInt = i;
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

输出结果:

pool-1-thread-1  给用户:0 办理业务
pool-1-thread-6  给用户:5 办理业务
pool-1-thread-5  给用户:4 办理业务
pool-1-thread-2  给用户:1 办理业务
pool-1-thread-4  给用户:3 办理业务
pool-1-thread-3  给用户:2 办理业务
pool-1-thread-10     给用户:9 办理业务
pool-1-thread-9  给用户:8 办理业务
pool-1-thread-8  给用户:7 办理业务
pool-1-thread-7  给用户:6 办理业务

2)Executor执行Callable任务

在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。

Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

import java.util.ArrayList;   
import java.util.List;   
import java.util.concurrent.*;   
  
public class CallableDemo{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
        List<Future<String>> resultList = new ArrayList<Future<String>>();   
  
        //创建10个任务并执行   
        for (int i = 0; i < 10; i++){   
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
            Future<String> future = executorService.submit(new TaskWithResult(i));   
            //将任务执行结果存储到List中   
            resultList.add(future);   
        }   
  
        //遍历任务的结果   
        for (Future<String> fs : resultList){   
                try{   
                    while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                    System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{   
                    //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
                    executorService.shutdown();   
                }   
        }   
    }   
}   
  
  
class TaskWithResult implements Callable<String>{   
    private int id;   
  
    public TaskWithResult(int id){   
        this.id = id;   
    }   
  
    /**  
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法, 
     * 则该方法自动在一个线程上执行 
     */   
    public String call() throws Exception {  
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
        //该返回结果将被Future的get方法得到  
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
    }   
}  
call()方法被自动调用!!!    pool-1-thread-1
call()方法被自动调用!!!    pool-1-thread-3
call()方法被自动调用!!!    pool-1-thread-2
call()方法被自动调用!!!    pool-1-thread-5
call()方法被自动调用!!!    pool-1-thread-4
call()方法被自动调用!!!    pool-1-thread-6
call()方法被自动调用!!!    pool-1-thread-9
call()方法被自动调用!!!    pool-1-thread-7
call()方法被自动调用!!!    pool-1-thread-8
call()方法被自动调用!!!    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:0    pool-1-thread-1
call()方法被自动调用,任务返回的结果是:1    pool-1-thread-2
call()方法被自动调用,任务返回的结果是:2    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:3    pool-1-thread-4
call()方法被自动调用,任务返回的结果是:4    pool-1-thread-5
call()方法被自动调用,任务返回的结果是:5    pool-1-thread-6
call()方法被自动调用,任务返回的结果是:6    pool-1-thread-7
call()方法被自动调用,任务返回的结果是:7    pool-1-thread-8
call()方法被自动调用,任务返回的结果是:8    pool-1-thread-9
call()方法被自动调用,任务返回的结果是:9    pool-1-thread-3

从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

4.5 自定义线程池

实际开发中,用Executor创建线程的方式比较少

为什么?

3.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。  
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。  
4.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。  
说明:Executors 返回的线程池对象的弊端如下:  
1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。  
2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。  
阿里巴巴《Java 开发手册》  

自定义线程池:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class MyThreadPoolExecutorDemo {
​
    public static void doSomething(ExecutorService executorService, int numOfRequest) {
        
        try {
​
            System.out.println(((ThreadPoolExecutor)executorService).getRejectedExecutionHandler().getClass() + ":");
            TimeUnit.SECONDS.sleep(1);
​
            for (int i = 0; i < numOfRequest; i++) {
                final int tempInt = i;
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
            
            TimeUnit.SECONDS.sleep(1);
            System.out.println("\n\n");
            
        } catch (Exception e) {
            System.err.println(e);
        } finally {
            executorService.shutdown();
        }
    }
    
    public static ExecutorService newMyThreadPoolExecutor(int corePoolSize,
           int maximumPoolSize, int blockingQueueSize, RejectedExecutionHandler handler){
        return new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                1,//keepAliveTime
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(blockingQueueSize),
/*
LinkedBlockingQueue*阻塞队列,直接用默认的构造函数
new LinkedBlockingQueue<Runnble>();     不赋初值,则是使用的构造函数是
    public LinkedBlockingQueue(){
        this(Integer.MAX_VALUE);
    }
初值太大消耗资源,尽量给一个初值
​
*/
                Executors.defaultThreadFactory(),
                handler);
    }
    
    
    public static void main(String[] args) {
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.AbortPolicy()), 10);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.CallerRunsPolicy()), 20);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardOldestPolicy()), 10);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardPolicy()), 10);
    }
​
}
class java.util.concurrent.ThreadPoolExecutor$AbortPolicy:
pool-1-thread-1  给用户:0 办理业务
pool-1-thread-3  给用户:5 办理业务java.util.concurrent.RejectedExecutionException: Task com.lun.concurrency.MyThreadPoolExecutorDemo$$Lambda$1/303563356@eed1f14 rejected from java.util.concurrent.ThreadPoolExecutor@7229724f[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]
​
pool-1-thread-2  给用户:1 办理业务
pool-1-thread-5  给用户:7 办理业务
pool-1-thread-3  给用户:3 办理业务
pool-1-thread-4  给用户:6 办理业务
pool-1-thread-1  给用户:2 办理业务
pool-1-thread-2  给用户:4 办理业务
class java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy:
pool-2-thread-1  给用户:0 办理业务
pool-2-thread-2  给用户:1 办理业务
pool-2-thread-1  给用户:2 办理业务
pool-2-thread-3  给用户:5 办理业务
pool-2-thread-3  给用户:7 办理业务
pool-2-thread-3  给用户:9 办理业务
pool-2-thread-4  给用户:6 办理业务
pool-2-thread-2  给用户:3 办理业务
pool-2-thread-5  给用户:8 办理业务
main     给用户:10 办理业务
pool-2-thread-1  给用户:4 办理业务
pool-2-thread-3  给用户:11 办理业务
pool-2-thread-4  给用户:13 办理业务
main     给用户:14 办理业务
pool-2-thread-1  给用户:12 办理业务
pool-2-thread-5  给用户:15 办理业务
pool-2-thread-2  给用户:17 办理业务
main     给用户:18 办理业务
pool-2-thread-3  给用户:16 办理业务
pool-2-thread-4  给用户:19 办理业务
​
​
​
class java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy:
pool-3-thread-1  给用户:0 办理业务
pool-3-thread-2  给用户:1 办理业务
pool-3-thread-1  给用户:2 办理业务
pool-3-thread-2  给用户:3 办理业务
pool-3-thread-3  给用户:5 办理业务
pool-3-thread-5  给用户:8 办理业务
pool-3-thread-2  给用户:7 办理业务
pool-3-thread-4  给用户:6 办理业务
pool-3-thread-1  给用户:4 办理业务
pool-3-thread-3  给用户:9 办理业务
​
​
​
class java.util.concurrent.ThreadPoolExecutor$DiscardPolicy:
pool-4-thread-1给用户:0 办理业务
pool-4-thread-2给用户:1 办理业务
pool-4-thread-1给用户:2 办理业务
pool-4-thread-2给用户:3 办理业务
pool-4-thread-3给用户:5 办理业务
pool-4-thread-3给用户:9 办理业务
pool-4-thread-1给用户:4 办理业务
pool-4-thread-5给用户:8 办理业务
pool-4-thread-4给用户:6 办理业务
pool-4-thread-2给用户:7 办理业务

4.6 线程池配置合理线程数

CPU密集型

  • CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

  • CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。

  • CPU密集型任务配置尽可能少的线程数量

一般公式:(CPU核数+1)个线程的线程池

lO密集型

  • 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数 * 2。

  • IO密集型,即该任务需要大量的IO,即大量的阻塞。

  • 在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

  • IO密集型时,大部分线程都阻塞,故需要多配置线程数;

参考公式:CPU核数/ (1-阻塞系数)

阻塞系数在0.8~0.9之间

比如8核CPU:8/(1-0.9)=80个线程数

参考文档:

https://blog.csdn.net/m0_37840000/article/details/79756932

https://www.cnblogs.com/dolphin0520/p/3932921.html

上一篇:池化层


下一篇:django报错 django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required; you ha