一、创建线程的方式

1、继承Thread

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 30; i++) {
  5. System.out.println("run:" + i);
  6. }
  7. }
  8. }
  9. public class Test {
  10. public static void main(String[] args) {
  11. MyThread myThread = new MyThread();
  12. myThread.start();
  13. for (int i = 0; i < 30; i++) {
  14. System.out.println(Thread.currentThread().getName());
  15. }
  16. }
  17. }

2、实现接口Runable

  1. public class RunnableImpl implements Runnable {
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 30; i++) {
  5. System.out.println(Thread.currentThread().getName() + ":" + i);
  6. }
  7. }
  8. }
  9. public class Test {
  10. public static void main(String[] args) {
  11. Thread thread = new Thread(new RunnableImpl());
  12. thread.start();
  13. for (int i = 0; i < 30; i++) {
  14. System.out.println(Thread.currentThread().getName() + ":" + i);
  15. }
  16. }
  17. }

3、实现接口Callable

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

  1. public class Test01 {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. TestCallable testCallable = new TestCallable();
  4. // FutureTask是Callable最重要的类,run方法在这里,它实现了runable
  5. FutureTask futureTask = new FutureTask(testCallable);
  6. Thread thread = new Thread(futureTask,"aaa");
  7. // 注意:如果用同一个futureTask,只会使用一个线程
  8. // Thread thread2 = new Thread(futureTask,"BBB");
  9. thread.start();
  10. // 待其计算完成后,再执行后续的流程
  11. while (!futureTask.isDone()){
  12. }
  13. System.out.println(futureTask.isDone());
  14. System.out.println(futureTask.get());
  15. System.out.println(futureTask.isDone());
  16. }
  17. }
  18. class TestCallable implements Callable<Integer> {
  19. @Override
  20. public Integer call() throws Exception {
  21. return new Random().nextInt(10);
  22. }
  23. }

二、创建线程池的方式

线程生命周期:新生、就绪、运行、死亡 �线程池是为了节省新生、就绪、死亡的时间

1、固定大小线程池

不允许用,底层用的LinkBlockingQueue()无限长度,Integer.MAX_VALUE,容易堆积大量请求,造成OOM。
image.png

  1. Executors.newFixedThreadPool(1);

以下代码演示三个三个任务打印:
1、制造阻塞2000ms,让所有线程启用并全部放入list,此时有三个线程是核心线程(它们已经开始执行,即重写的方法call()已经开始执行),另外的47个线程在阻塞队列(它们还没开始执行,及重写的方法call() 还没执行)。
2、当线程全部启动之后,进入List.forEach循环,此时future.get()核心线程(阻塞2000ms中),待核心线程阻塞结束,便会执行并输出,此时会循环3次,输出核心线程中的三个线程。
3、接着阻塞队列中的47个会出来3个进入核心线程,并继续阻塞2000ms,然后接着输出,依次循环至全部输出结束。

  1. public class callableTest02 {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. ExecutorService executorService = Executors.newFixedThreadPool(3);
  4. List<Future> list = new ArrayList<>();
  5. // 先启动所有线程,然后在再执行
  6. for (int i = 0; i < 50; i++) {
  7. Future<String> future = executorService.submit(new Callable<String>() {
  8. @Override
  9. public String call() throws Exception {
  10. // 制造阻塞
  11. Thread.sleep(2000);
  12. return Thread.currentThread().getName();
  13. }
  14. });
  15. list.add(future);
  16. }
  17. // 将执行的结果放到list里面,通过list打印,及每次执行线程池中的三个核心线程
  18. list.forEach(future -> {
  19. try {
  20. System.out.println(future.get());
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } catch (ExecutionException e) {
  24. e.printStackTrace();
  25. }
  26. });
  27. executorService.shutdown();
  28. }
  29. }
  30. 输出:
  31. pool-1-thread-1
  32. pool-1-thread-2
  33. pool-1-thread-3

2、单线程池

需要保证顺序执行各个任务的场景
不允许用,底层用的LinkBlockingQueue()无限长度,Integer.MAX_VALUE,容易堆积大量请求,造成OOM。
image.png

  1. Executors.newSingleThreadExecutor()
  1. public class newSingleThreadExecutor {
  2. public static void main(String[] args) {
  3. ExecutorService executorService = Executors.newSingleThreadExecutor();
  4. for (int i = 0; i < 100; i++) {
  5. executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
  6. }
  7. executorService.shutdown();
  8. }
  9. }

3、周期性线程池

该线程池可以执行定时任务
不允许使用,底层创建大量线程,Integer.MAX_VALUE,容易堆积大量请求,造成OOM。
image.png
image.png

  1. Executors.newScheduledThreadPool(3);
  1. public class newScheduledThreadPoolTest {
  2. public static void main(String[] args) {
  3. ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
  4. for (int i = 0; i < 100; i++) {
  5. executorService.schedule(new Runnable() {
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName());
  9. }
  10. // 三秒后执行
  11. }, 3, TimeUnit.SECONDS);
  12. }
  13. executorService.shutdown();
  14. }
  15. }

4、缓存线程池

无限创建线程( Integer.MAX_VALUE)
不允许使用,底层创建大量线程,Integer.MAX_VALUE,容易堆积大量请求,造成OOM。
image.png

  1. Executors.newCachedThreadPool()
  1. public class newCachedThreadPoolTest {
  2. public static void main(String[] args) {
  3. ExecutorService executorService = Executors.newCachedThreadPool();
  4. for (int i = 0; i < 1; i++) {
  5. executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
  6. }
  7. executorService.shutdown();
  8. }
  9. }

三、自定义线程池

1、线程池7大参数

  1. public ThreadPoolExecutor(int corePoolSize, //常驻核心线程(必定干活的)
  2. int maximumPoolSize, //线程池能够容纳的最大线程数(包含核心线程)
  3. long keepAliveTime, //多余的空闲线程(超过corePoolSize数量的线程)的存活时间
  4. TimeUnit unit, // 时间单位
  5. BlockingQueue<Runnable> workQueue, //等待区
  6. ThreadFactory threadFactory, // 线程工厂,用于创建线程的一些属性,例如名称
  7. RejectedExecutionHandler handler)
  1. public class ThreadPool02 {
  2. public static void main(String[] args) {
  3. ExecutorService executorService = new ThreadPoolExecutor(
  4. // corePoolSize
  5. 5,
  6. // maximumPoolSize
  7. 5,
  8. // keepAliveTime
  9. 60,
  10. // TimeUnit unit
  11. TimeUnit.SECONDS,
  12. new LinkedBlockingQueue<>(5),
  13. new ThreadFactoryBuilder().setNameFormat("itemPushThread-%d").setDaemon(true).build(),
  14. new ThreadPoolExecutor.DiscardPolicy()
  15. );
  16. }
  17. }

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。 User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

2、线程池运行原理

1.在创建了线程池后,等待提交过来的任务请求。
2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务; 2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于corePoolSize,那么多出来的线程就被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

四、拒绝策略

1、AbortPolicy

这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

2、DiscardPolicy

这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

3、DiscardOldestPolicy

如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

4、CallerRunsPolicy

相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处:

4.1 第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。
4.2 第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

五、合理配合线程池

1、CPU密集型:该任务需要大量运算,而且没有阻塞,CPU一直在全速运行(例如一个for循环无限跑)
CPU密集型任务只有在真正的多核CPU上才能得到加速(通过多线程),如果是单核CPU上,无论你开几个模拟的多线程,该任务都不能得到加速,因为CPU总的运算能力就那么多。尽可能少的线程数量:一般公式:CPU核数+1个线程 的线程池,即8核CPU,可以设置9个线程
2、IO密集型:不是一直在执行任务,例如(不停的去数据库、redis拿数据,存数据)
方案一:应配置尽可能多的线程,如CPU核数*2
方案二:CPU核数/1-阻塞系数 阻塞系数在0.8-0.9之间,比如8核CPU:8/(1-0.9)=80个线程数