Java 并发编程(五)-线程池一

面试题:
1.线程池的工作原理,几个重要参数?阻塞队列的作用是什么?底层如何实现的?
2.线程池的构造类的方法的5个参数的具体意义?
3.单机上一个线程池正在处理服务如果忽然断电怎么办(正在处理和阻塞队列里的请求怎么处理)?
4.使用无界阻塞队列会出现什么问题?
5.线程池的拒绝策略请你谈谈?
6.你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用哪个?
  答案:一个都不用,我们生产上只能使用自定义的
7.Executors中JDK已经给你提供了,为什么不用?
  阿里巴巴Java开发手册
  3.[强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
  说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源
不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者"过度切换"的问题。
  4.[强制]线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的
处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
  说明:Executors返回的线程池对象的弊端如下:
    1)FixedThreadPool和SingleThreadPool:
      允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
    2)CachedThreadPool和ScheduledThreadPool:
      允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量的线程,从而导致OOM。
8.你在工作中是如何使用线程池的,是否自定义过线程池使用?
9.合理配置线程池最大线程数量你是如何考虑的?

目录

一、并发编程

1、线程池

1.1、线程池的架构

1.2、线程池使用方式

1.2.1、newFixedThreadPool()

1.2.2、newSingleThreadExecutor();

1.2.3、newCachedThreadPool();

1.2.4、newScheduleThreadPool

1.2.5、newSingleThreadScheduledExecutor

1.2.6、newWorkStealingPool

1.3、线程池底层原理

1.4、线程池的七个参数

1.5、线程池底层工作原理

1.6、拒绝策略

1.6.1、AbortPolic(默认)

1.6.2、CallerRunsPolicy

1.6.3、DiscardOldestPolicy

1.6.4、DiscardPolicy

1.7、自定义线程池

1.8、设置线程池名称

方式1:Spring 框架提供的 CustomizableThreadFactory

方式2:Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。

方式3:Apache commons-lang3 提供的 BasicThreadFactory

方式4:自定义一个ThreadFactory


一、并发编程

1、线程池

    线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销。进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

为什么用线程池,优势?

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

线程池的主要特点:

    它的主要特点为:线程复用:控制最大并发数,管理线程。

        1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

        2、提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。

        3、提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

1.1、线程池的架构

    Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。

1.2、线程池使用方式

Executors工具类

Executors常用方法:
1.static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的
线程池,以共享的无界队列方式来运行这些线程。
2.static ExecutorService newSingleThreadExecutor():创建一个使用单个 worker 线程的
Executor,以无界队列方式来运行该线程。
3.static ExecutorService newCachedThreadPool():创建一个可根据需要创建新线程的线程池,
但是在以前构造的线程可用时将重用它们。 

Executors.newFixedThreadPool(int nThreads):一池N线程。
Executors.newSingleThreadExecutor():一个任务一个任务执行,一池一线程。
Executors.newCachedThreadPool():线程池根据需求创建线程,可扩容,遇强则强。
1.2.1、newFixedThreadPool()

示例:一池N线程:执行长期的任务,性能好很多

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {

	public static void main(String[] args) {
		ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
		try {
			for (int i = 1; i <=10; i++) {
				threadPool1.execute(()->{
					System.out.println(Thread.currentThread().getName()+" 办理业务");
				});
			}
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool1.shutdown();
		}
	}
}
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

newFixedThreadPool主要特点如下:

    1、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

    2、newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

1.2.2、newSingleThreadExecutor();

示例:一池一线程:一个任务一个任务执行的场景

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {

	public static void main(String[] args) {
		ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
		
		try {
			for (int i = 1; i <=10; i++) {
				threadPool2.execute(()->{
					System.out.println(Thread.currentThread().getName()+" 办理业务");
				});
			}
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool2.shutdown();
		}
	}
}
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

newSingleThreadExecutor主要特点如下:

    1、创建一个单线程化线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

    2、newSingleThreadExecutor创建的线程池corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue;

1.2.3、newCachedThreadPool();

示例:适用:执行很多短期异步的小程序或者负载较轻的服务器

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {

	public static void main(String[] args) {
		ExecutorService threadPool3 = Executors.newCachedThreadPool();
		
		try {
			for (int i = 1; i <=10; i++) {
				threadPool3.execute(()->{
					System.out.println(Thread.currentThread().getName()+" 办理业务");
				});
			}
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool3.shutdown();
		}
	}
}
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

newCachedThreadPool主要特点如下:

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

    2、newCachedThreadPool将corePoolSize设置为0和maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

1.2.4、newScheduleThreadPool

    首先看到名字就可以猜到当前线程池是一个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者是延迟多久执行一个任务一次

public static void main(String[] args) {
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
    // 正常执行
    pool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + ":1");
    });

    // 延迟执行,执行当前任务延迟5s后再执行
    pool.schedule(() -> {
        System.out.println(Thread.currentThread().getName() + ":2");
    }, 5, TimeUnit.SECONDS);

    // 周期执行,当前任务第一次延迟5s执行,然后每3s执行一次
    // 这个方法在计算下次执行时间时,是从任务刚刚开始时就计算。
    pool.scheduleAtFixedRate(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() + ":3");
    }, 5, 3, TimeUnit.SECONDS);

    // 周期执行,当前任务第一次延迟5s执行,然后每3s执行一次
    // 这个方法在计算下次执行时间时,会等待任务结束后,再计算时间
    pool.scheduleWithFixedDelay(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() + ":4");
    }, 5, 1, TimeUnit.SECONDS);
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
//..

    所以本质上还是正常线程池,只不过在原来的线程池基础上实现了定时任务的功能
原理是基于DelayQueue实现的延迟执行周期性执行是任务执行完毕后,再次扔回到阻塞队列

1.2.5、newSingleThreadScheduledExecutor

    Executors提供的newSingleThreadScheduledExecutor单例的定时任务线程池

    一个线程的线程池可以延迟或者以一定的周期执行一个任务。

1.2.6、newWorkStealingPool

    当前JDK提供构建线程池的方式newWorkStealingPool和之前的线程池很非常大的区别
1、之前定长,单例,缓存,定时任务都基于ThreadPoolExecutor去实现的。

2、newWorkStealingPool是基于ForkJoinPool构建出来的

    ThreadPoolExecutor的核心点:在ThreadPoolExecutor中只有一个阻塞队列存放当前任务。

    ForkJoinPool的核心特点:ForkJoinPool从名字上就能看出一些东西。当有一个特别大的任务时,如果采用上述方式,这个大任务只能会某一个线程去执行。ForkJoin第一个特点是可以将一个大任务拆分成多个小任务,放到当前线程的阻塞队列中。其他的空闲线程就可以去处理有任务的线程的阻塞队列中的任务。

    案例:有一个比较大的数组,里面存满值,计算总和

单线程处理一个任务:

/**
 * 非常大的数组
 */
static int[] nums = new int[1_000_000_000];

// 填充值
static {
    for (int i = 0; i < nums.length; i++) {
        nums[i] = (int) ((Math.random()) * 1000);
    }
}

public static void main(String[] args) {
    // ===========单线程累加10亿数据================
    System.out.println("单线程计算数组总和!");
    long start = System.currentTimeMillis();
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    long end = System.currentTimeMillis();
    System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (double) (end - start) / 1000d);
}

    多线程分而治之的方式处理:

/**
 * 非常大的数组
 */
static int[] nums = new int[1_000_000_000];

// 填充值
static {
    for (int i = 0; i < nums.length; i++) {
        nums[i] = (int) ((Math.random()) * 1000);
    }
}

public static void main(String[] args) {
    // ===================多线程分而治之累加10亿数据================================
    // 在使用forkJoinPool时,不推荐使用Runnable和Callable
    // 可以使用提供的另外两种任务的描述方式
    // Runnable(没有返回结果) ->   RecursiveAction
    // Callable(有返回结果)   ->   RecursiveTask
    ForkJoinPool forkJoinPool = (ForkJoinPool) Executors.newWorkStealingPool();
    System.out.println("分而治之计算数组总和!");
    long forkJoinStart = System.currentTimeMillis();
    ForkJoinTask<Integer> task = forkJoinPool.submit(new SumRecursiveTask(0, nums.length - 1));
    Integer result = task.join();
    long forkJoinEnd = System.currentTimeMillis();
    System.out.println("分而治之运算结果为:" + result + ",计算时间为:" + (forkJoinEnd - forkJoinStart) / 1000d);
}

private static class SumRecursiveTask extends RecursiveTask<Integer> {
    /**
     * 指定一个线程处理哪个位置的数据
     */
    private int start, end;
    private final int MAX_STRIDE = 100_000_000;
    //  200_000_000: 147964900
    //  100_000_000: 145942100

    public SumRecursiveTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        // 在这个方法中,需要设置好任务拆分的逻辑以及聚合的逻辑
        int sum = 0;
        int stride = end - start;
        if (stride <= MAX_STRIDE) {
            // 可以处理任务
            for (int i = start; i <= end; i++) {
                sum += nums[i];
            }
        } else {
            // 将任务拆分,分而治之。
            int middle = (start + end) / 2;
            // 声明为2个任务
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            // 分别执行两个任务
            left.fork();
            right.fork();
            // 等待结果,并且获取sum
            sum = left.join() + right.join();
        }
        return sum;
    }
}

    最终可以发现,这种累加的操作中,采用分而治之的方式效率提升了2倍多。但是也不是所有任务都能拆分提升效率,首先任务得大,耗时要长。

1.3、线程池底层原理

    ExecutorService threadPool1 = Executors.newFixedThreadPool(5);

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

    ExecutorService threadPool2 = Executors.newSingleThreadExecutor();

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

    ExecutorService threadPool3 = Executors.newCachedThreadPool();

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

上面三个多线程底层创建都是new ThreadPoolExecutor();

1.4、线程池的七个参数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

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

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

        ②.当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
2、int maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
3、long keepAliveTime:多余的空闲线程的存活时间。

    当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
4、TimeUnit unit:keepAliveTime的单位。
5、BlockingQueue<Runnable> workQueue:任务队列,被提交但尚未被执行的任务。
6、ThreadFactory threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
7、RejectedExecutionHandler handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝

1.5、线程池底层工作原理

如上图所示:

    线程池工作流程:线程池执行execute()方法,才开始创建线程。

    线程池先执行①corePool(常驻线程)中的任务,其次把任务放到②阻塞队列中,再其次任务放到③最大线程池剩余空间中(并新创建线程执行③中的任务,而②中的阻塞任务会继续等待),还超过上述所有个数之和的情况下,会启动④也就是拒绝策略生效了。

---------------------------------------------------------------------------------------------

线程池的底层工作原理?

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

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

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

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

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

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

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

4、当一个线程无事可做超过一定时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

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;

public class ThreadPoolDemo2 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 100L, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy());
		try {
			for (int i = 1; i <= 8; i++) {
				final int tmpI = i;
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName() + " 办理业务" + tmpI);
					try {
						TimeUnit.SECONDS.sleep(4);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}

}
pool-1-thread-2 办理业务2
pool-1-thread-5 办理业务8
pool-1-thread-3 办理业务6
pool-1-thread-1 办理业务1
pool-1-thread-4 办理业务7
pool-1-thread-5 办理业务3
pool-1-thread-2 办理业务4
pool-1-thread-1 办理业务5

上面运行结果,办理业务6,7,8直接抢占扩容的非核心线程进行工作。最后执行阻塞队列中的3,4,5任务。

1.6、拒绝策略

拒绝策略是什么?

    等待队列也已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。

JDK内置拒绝策略:

    1、AbortPolic(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      throw new RejectedExecutionException("Task " + r.toString() +
                                           " rejected from " +
                                           e.toString());
  }

    2、CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      if (!e.isShutdown()) {
          r.run();
      }
  }

    3、DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中,尝试再次提交当前任务。

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      if (!e.isShutdown()) {
          e.getQueue().poll();
          e.execute(r);
      }
  }

    4、DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常,如果允许任务丢失,这是最好的策略。

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  }

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

    5、自定义Policy:根据自己的业务,可以将任务扔到数据库,也可以做其他操作。

private static class MyRejectedExecution implements RejectedExecutionHandler{
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("根据自己的业务情况,决定编写的代码!");
    }
}
1.6.1、AbortPolic(默认)
package com.lwz.day04;

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;

public class ThreadPoolDemo3 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy());
		try {
			for (int i = 1; i <= 9; i++) {
				final int tmpI = i;
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName() + " 办理业务" + tmpI);
					try {
						TimeUnit.SECONDS.sleep(4);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}

}

结果:

pool-1-thread-1 办理业务1
pool-1-thread-4 办理业务7
pool-1-thread-5 办理业务8
pool-1-thread-3 办理业务6
pool-1-thread-2 办理业务2
java.util.concurrent.RejectedExecutionException: Task com.lwz.day04.ThreadPoolDemo3$$Lambda$1/303563356@eed1f14 rejected from java.util.concurrent.ThreadPoolExecutor@7229724f[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.lwz.day04.ThreadPoolDemo3.main(ThreadPoolDemo3.java:21)
pool-1-thread-2 办理业务3
pool-1-thread-4 办理业务4
pool-1-thread-3 办理业务5

超过max+阻塞队列的数量后报RejectedExecutionException异常

1.6.2、CallerRunsPolicy
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;

public class ThreadPoolDemo3 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.CallerRunsPolicy());
		try {
			for (int i = 1; i <= 9; i++) {
				final int tmpI = i;
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName() + " 办理业务" + tmpI);
					try {
						TimeUnit.SECONDS.sleep(4);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}

}

结果

pool-1-thread-1 办理业务1
pool-1-thread-5 办理业务8
main 办理业务9
pool-1-thread-4 办理业务7
pool-1-thread-3 办理业务6
pool-1-thread-2 办理业务2
pool-1-thread-3 办理业务3
pool-1-thread-1 办理业务4
pool-1-thread-2 办理业务5
1.6.3、DiscardOldestPolicy
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;

public class ThreadPoolDemo3 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.DiscardOldestPolicy());
		try {
			for (int i = 1; i <= 9; i++) {
				final int tmpI = i;
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName() + " 办理业务" + tmpI);
					try {
						TimeUnit.SECONDS.sleep(4);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}

}

结果:业务3被丢弃

pool-1-thread-1 办理业务1
pool-1-thread-5 办理业务8
pool-1-thread-4 办理业务7
pool-1-thread-2 办理业务2
pool-1-thread-3 办理业务6
pool-1-thread-1 办理业务4
pool-1-thread-5 办理业务9
pool-1-thread-3 办理业务5
1.6.4、DiscardPolicy
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;

public class ThreadPoolDemo3 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.DiscardPolicy());
		try {
			for (int i = 1; i <= 9; i++) {
				final int tmpI = i;
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName() + " 办理业务" + tmpI);
					try {
						TimeUnit.SECONDS.sleep(4);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}

}

结果:任务9无法处理被丢弃

pool-1-thread-3 办理业务6
pool-1-thread-5 办理业务8
pool-1-thread-4 办理业务7
pool-1-thread-1 办理业务1
pool-1-thread-2 办理业务2
pool-1-thread-5 办理业务3
pool-1-thread-2 办理业务5
pool-1-thread-1 办理业务4

1.7、自定义线程池

为什么要自定义线程池?

    首先ThreadPoolExecutor中,一共提供了7个参数,每个参数都是非常核心的属性,在线程池去执行任务时,每个参数都有决定性的作用。

    但是如果直接采用JDK提供的方式去构建,可以设置的核心参数最多就两个,这样就会导致对线程池的控制粒度很粗。所以在阿里规范中也推荐自己去自定义线程池。手动的去new ThreadPoolExecutor设置他的一些核心属性。

    自定义构建线程池,可以细粒度的控制线程池,去管理内存的属性,并且针对一些参数的设置可能更好的在后期排查问题。

ThreadPoolExecutor提供的七个核心参数

public ThreadPoolExecutor(
    int corePoolSize,           // 核心工作线程(当前任务执行结束后,不会被销毁)
    int maximumPoolSize,        // 最大工作线程(代表当前线程池中,一共可以有多少个工作线程)
    long keepAliveTime,         // 非核心工作线程在阻塞队列位置等待的时间
    TimeUnit unit,              // 非核心工作线程在阻塞队列位置等待时间的单位
    BlockingQueue<Runnable> workQueue,   // 任务在没有核心工作线程处理时,任务先扔到阻塞队列中
    ThreadFactory threadFactory,         // 构建线程的线程工作,可以设置thread的一些信息
    RejectedExecutionHandler handler) {  // 当线程池无法处理投递过来的任务时,执行当前的拒绝策略
    // 初始化线程池的操作
}

面试题:你在工作中是如何使用线程池的,是否自定义过线程池使用?

面试题:合理配置线程池最大线程数量你是如何考虑的?

1、业务:CPU密集型:

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

        CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

        而在单核CPU,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。

CPU密集型任务配置尽可能少的线程数量:一般公式:CPU核数+1个线程的线程池

2、业务:IO密集型:

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

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

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

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

参考公式:CPU核数/1-阻塞系数               阻塞系数在0.8-0.9之间

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

JAVA代码获取CPU核数

// cpu数目
int cpu = Runtime.getRuntime().availableProcessors();

自定义线程池示例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo2 {

	public static void main(String[] args) {
		ExecutorService threadPool = new ThreadPoolExecutor(2,5,2L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3)
				,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
		try {
			for (int i = 1; i <=10; i++) {
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName()+" 办理业务");
				});
			}
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			threadPool.shutdown();
		}
	}
}

代码构建线程池,并处理有无返回结果的任务

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //1. 构建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            2,
            5,
            10,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("test-ThreadPoolExecutor");
                    return thread;
                }
            },
            new MyRejectedExecution()
    );

    //2. 让线程池处理任务,没返回结果
    threadPool.execute(() -> {
        System.out.println("没有返回结果的任务");
    });

    //3. 让线程池处理有返回结果的任务
    Future<Object> future = threadPool.submit(new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            System.out.println("我有返回结果!OK");
            return "OK";
        }
    });
    Object result = future.get();
    System.out.println("返回结果:"+result);

    //4. 如果是局部变量的线程池,记得用完要shutdown
    threadPool.shutdown();
}

private static class MyRejectedExecution implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("根据自己的业务情况,决定编写的代码!");
    }
}

1.8、设置线程池名称

    阿里规范规定,【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯,怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

方式1:Spring 框架提供的 CustomizableThreadFactory
ThreadFactory threadFactory = new CustomizableThreadFactory("功能-pool-");
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
								10, 
								20, 
								5, 
								TimeUnit.MINUTES, 
								new ArrayBlockingQueue<Runnable>(10),
								threadFactory ); //给线程池中的线程自定义名称
方式2:Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("功能-pool-").build();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
								10, 
								20, 
								5, 
								TimeUnit.MINUTES, 
								new ArrayBlockingQueue<Runnable>(10),
								threadFactory); //给线程池中的线程自定义名称
方式3:Apache commons-lang3 提供的 BasicThreadFactory
ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("功能-pool-").build();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
								10, 
								20, 
								5, 
								TimeUnit.MINUTES, 
								new ArrayBlockingQueue<Runnable>(10),
								threadFactory ); //给线程池中的线程自定义名称
方式4:自定义一个ThreadFactory

自定义NamesThreadFactory

public class NamesThreadFactory implements ThreadFactory{
	
	private final ThreadGroup group;
	private final AtomicInteger threadNum = new AtomicInteger(1);
	private final String namePrefix;

	public NamesThreadFactory(String name) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        if (null == name || name.isEmpty()) {
            name = "pool";
        }
        namePrefix = name + "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNum.getAndIncrement(), 0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

使用

NamesThreadFactory threadFactory = new NamesThreadFactory("namesThread-");
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
								10, 
								20, 
								5, 
								TimeUnit.MINUTES, 
								new ArrayBlockingQueue<Runnable>(10),
								threadFactory); //给线程池中的线程自定义名称

Java 并发编程(四)-阻塞队列

Java 并发编程(六)-线程池二

每天⽤⼼记录⼀点点。内容也许不重要,但习惯很重要!
一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀神lwz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值