JUC并发编程学习笔记(十)线程池(重点)

线程池(重点)

线程池:三大方法、七大参数、四种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!优化资源的使用!-> 池化技术(线程池、连接池、对象池......);创建和销毁十分消耗资源

池化技术:事先准备好一些资源,有人要用就拿,拿完用完还给我。

线程池的好处:

1、降低资源消耗

2、提高相应速度

3、方便管理

线程复用、可以控制最大并发数、管理线程

线程池:三大方法

1、newSingleThreadExecutor

单列线程池,只有一条线程;

单例线程池配合callable使用,注意需要在程序运行结束后关闭线程池

java 复制代码
package org.example.pool;

import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//Executors->工具类
public class Demo01 {
    public static void main(String[] args) {
        TestCallable able = new TestCallable();

        //三大方法
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程


        try {
            for (int i = 0; i < 10; i++) {
                FutureTask<String> task = new FutureTask<String>(able);

                //线程池创建线程
                threadPool.execute(task);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }


//        Executors.newFixedThreadPool(5);//固定线程池大小
//        Executors.newCachedThreadPool();//可伸缩的线程池
    }
}

class TestCallable implements Callable<String> {
    Lock lock = new ReentrantLock();

    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName() + ":ok");
        return Thread.currentThread().getName() + ":ok";
    }
}

注意结果图

在单例线程池中,运行结果发现都是同一条线程在操作资源,整个线程池中只有一条pool-1-thread-1线程。

2、newFixedThreadPool

同样的代码,将线程池切换为固定大小的线程池,设置为5条,这样跑出来的结果又不一样

由于设置了线程休眠,所以会导致比较平均的结果出现,但是一般情况下都是五条线程抢占资源,每次结果都是不一定的,看那条线程处理的比较快抢占的比较多。

3、newCachedThreadPool

同样的代码,将线程池切换为可伸缩大小的线程池,这样跑出来的结果又不一样

根据业务代码生成具体条数的线程:如本次业务通过循环或其他因数,同时需要处理10条任务,那么当你线程池中的第一条线程还未完成任务时就会生成一条新的线程来同步处理这些任务,只要你cpu处理速度够快那么理论最高可能同时生成一个具有10条线程(任务数)的一个线程池。

七大参数

源码分析

java 复制代码
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
java 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    //和以上没有区别,只是通过用户调用来完成的,相当于new ThreadPoolExecutor(5, 5,....)
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
java 复制代码
public static ExecutorService newCachedThreadPool() {
    //设置了默认是0个线程,但是最大值可以达到大约21亿条,设置了Integer.MAX_VALUE(约21亿)
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//如果通过Integer.MAX_VALUE来跑线程池一定会照成OOM(溢出)
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通过观察以上三大方法的创建线程池方式,可以发现,三大方法的本质都是调用ThreadPoolExecutor来创建的

java 复制代码
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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

这就是为什么阿里巴巴开发规范中说不要使用Executors来创建线程池而是让我们通过ThreadPoolExecutor来创建,其实就是让我们通过了解线程池的本质来避免一些问题。

模拟银行业务模块模拟

java 复制代码
package org.example.pool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo02 {
    public static void main(String[] args) {
        //自定义线程池,工作中只会使用ThreadPoolExecutor
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                new ThreadPoolExecutor.AbortPolicy());//拒绝策略,即当阻塞队列和线程池线程都已经最大化运行没有任何位置可以处置接下来的元素了,就拒绝该元素进入并抛出异常
        try {
            //最大承载 队列Queue+max
            for (int i = 0; i < 10; i++) {
                //线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

此时有10个任务,但是最大线程量只有5个,阻塞队列量只有三个,所以注定会有两个无法处理,此时触发拒绝策略,抛出异常并且拒绝该任务。

可以看到执行了八个任务,通过五条不同的线程。执行了拒绝策略抛出了异常java.util.concurrent.RejectedExecutionException

四种拒绝策略

四种拒绝策略的描述

java 复制代码
//AbortPolicy 队列满了,丢掉任务,抛出异常
//CallerRunsPolicy 哪条线程给的任务回到哪条线程去执行,线程池不执行
//DiscardPolicy 队列满了,丢掉任务,但不抛出异常
//DiscardOldestPolicy 队列满了,尝试去和最早的竞争,如果没成功,依旧丢弃任务,但不抛出异常

小结和拓展

了解:IO密集型、cpu密集型(调优)

java 复制代码
/*
* 最大线程到底该如何定义
* 1、CPU 密集型,几何就定义为几,就可以保证cpu效率最高的
* 2、IO 密集型 > 判断你程序中十分耗IO的线程,
* 程序    15个大小任务 io十分占用资源
* */
相关推荐
神仙别闹几秒前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
小爬虫程序猿1 分钟前
利用Java爬虫速卖通按关键字搜索AliExpress商品
java·开发语言·爬虫
组合缺一6 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·后端·spring·solon
程序猿零零漆8 分钟前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
java·spring cloud·mybatis-plus
猿来入此小猿10 分钟前
基于SpringBoot在线音乐系统平台功能实现十二
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
愤怒的代码24 分钟前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端
带多刺的玫瑰24 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
栗豆包40 分钟前
w118共享汽车管理系统
java·spring boot·后端·spring·tomcat·maven
夜半被帅醒1 小时前
MySQL 数据库优化详解【Java数据库调优】
java·数据库·mysql
万亿少女的梦1681 小时前
基于Spring Boot的网络购物商城的设计与实现
java·spring boot·后端