聊聊Java中的线程池(ExecutorService)

聊聊Java中的线程池(ExecutorService)

本章节的源代码位于gitee上,想要下载的请点击线程池

为什么要使用线程池

聊到线程,就要谈谈什么是进程,它和线程有什么关系呢?

进程:

  • 每个程序启动都会开启一个进程
  • 系统进行资源调度的基础
  • 每个进程都在内存中有一块独立的空间
  • 进程里的方法区,是用来存放进程中的代码片段的,是线程共享的
  • 在多线程 OS 中,进程不是一个可执行的实体,即一个进程至少创建一个线程去执行代码

线程:

  • CPU 调度和分派的基本单位
  • 不能独立存在
  • 线程不是一直执行的
  • 线程使用的内存是所在进程的内存
  • 线程只拥有在运行中必不可少的资源(如程序计数器、栈)
  • 程序计数器用于标记该时间片结束后,程序执行到的位置,方便下一次继续往后执行
  • 每个线程都有自己独立的栈资源,其他线程无权访问。用于存放该线程的局部变量和调用栈帧

关系:

  • 一个程序至少一个进程,一个进程至少一个线程,进程中多个线程共享进程资源
  • 启动Java中的main函数就会启动一个线程,该线程称之为主线程。同时还会启动一个gc线程。
  • 一个进程有多个线程,多个线程共享进程的堆和方法区,但是每个线程都有自己的栈和程序计数器。

我们大概的聊了聊线程与进程的区别,如果想详细了解两种的管理和区别,请查看百度百科的进程线程

下面我们就来聊聊为什么要使用线程池:我们都知道Java是没有权限区操作计算机底层的,所有Java提供了一个native关键字,这个关键字就是Java通过其他语言来为Java提供一些Java无法做到的事情。我们在学多线程的时候使用Thread类中的start()方法来启动多线程。而该类什么都没有做,直接是调用了一个莫名其妙的方法start0(),而该方法就是使用的native关键字标记的,而且没有实现方法。

public synchronized void start() {
    if (threadStatus != 0) throw new IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}
private native void start0();

既然Java无法主动调用系统底层,那么每一次创建线程,都需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。而在一个项目的使用过程中,高并发是常有的事情,如果每个线程都要重复创建、使用、销毁的过程,那么用户体验就会很差。基于如此,我们就应该使用线程池来将线程统一管理。

线程池说白了就是多个线程的一个集合,这些线程在第一次使用的时候就会被创建,然后需要的时候就直接从池里面取出一个线程,使用完了就放回池中,省去了创建和销毁的过程,提高了程序响应速度。

创建线程池

通过ThreadPoolExecutor实现线程池的创建

在使用ThreadPoolExecutor类的时候需要注意,该类需要传入七个参数,它们分别代表着:核心线程数(常驻线程)、最大线程数、存活时间、时间单位、等待队列、线程工厂、拒绝策略。相关源代码如下:

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中提供的defaultThreadFactory()即可,需要注意的一点是拒绝策略ThreadPoolExecutor类提供了四种,不同的拒绝策略代表了不同的操作方式。它们分别是:

  • AbortPolicy:默认的拒绝策略,throws RejectedExecutionException
  • CallerRunsPolicy:提交任务的线程自己去执行该任务
  • DiscardPolicy:直接丢弃任务,不抛出任何异常
  • DiscardOldestPolicy:丢弃最老的任务,加入新的任务

最后我们通过作图来看看着七个参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHeL2X6D-1603503169697)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184317289.png)]

接下来就是实际操作环节了。当我们的拒绝策略设置为默认的时候,当线程请求量没有达到最大时(最大线程数+队列数):

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        /**
         * 参数一:核心线程数(常驻线程)
         * 参数二:最大线程数
         * 参数三:存活时间
         * 参数四:时间单位
         * 参数五:等待队列
         * 参数六:线程工厂
         * 参数七:拒绝策略
         */
		ExecutorService executorService = new ThreadPoolExecutor(3,5,1L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
		for (int i = 0; i < 8; i++) {
			executorService.execute(()->{
				System.out.println(Thread.currentThread().getName());
			});
		}
		executorService.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zppjNqDs-1603503169708)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184459545.png)]

但是如果我们的请求量超过了最大接收值后呢?for (int i = 0; i < 10; i++) {……}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhRiE26H-1603503169713)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184621696.png)]

这个时候就会抛出异常,解决这个问题在于解决策略上。

使用Executors类创建线程池

Executors 类是从 JDK 1.5 开始就新增的线程池创建的静态工厂类,它就是创建线程池的。使用该类有6种创建方法,加下来,一一实现。

1、newFixedThreadPool创建定长线程池

public class NewFixedThreadPoolDemo {
    public static void main(String[] args) {
        //创建工作线程数为 3 的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i <6; i++) {
            fixedThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() );
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {}
        System.out.println("10毫秒后...");
        //关闭线程池
        fixedThreadPool.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXm5H6H2-1603503169717)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023201649855.png)]

2. newCachedThreadPool创建可缓存的线程池

public class NewFixedThreadPoolDemo {
    public static void main(String[] args) {
        //创建工作线程数为 3 的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i <6; i++) {
            fixedThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() );
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {}
        System.out.println("10毫秒后...");
        //关闭线程池
        fixedThreadPool.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dv2Qdov5-1603503169720)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202021861.png)]

3. newScheduledThreadPool创建定长线程池,可执行周期性的任务。

public class NewScheduledThreadPoolDemo {
    public static void main(String[] args) {
        //创建定长线程池,可执行周期性的任务
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        for (int i = 0; i <3; i++) {
            scheduledThreadPool.scheduleWithFixedDelay(() -> {
                System.out.println(Thread.currentThread().getName());
            }, 0, 3, TimeUnit.SECONDS);
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {}
        System.out.println("10毫秒后...");
        //关闭线程池
        scheduledThreadPool.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVdrPFZs-1603503169723)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202227254.png)]

4. newSingleThreadExecutor创建单线程的线程池

public class NewSingleThreadExecutorDemo {
    public static void main(String[] args) {
        //单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for (int i = 0; i <3; i++) {
            final int index = i;
            singleThreadPool.execute(() -> {
                if (index == 1) {
                    throw new RuntimeException("线程执行出现异常");
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {}
        System.out.println("10毫秒后...");
        //关闭线程池
        singleThreadPool.shutdown();
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJfnR9q5-1603503169725)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202501956.png)]

5. newSingleThreadScheduledExecutor创建单线程可执行周期性任务的线程池

public class NewSingleThreadScheduledExecutorDemo {
    public static void main(String[] args) {
        //创建单线程可执行周期性任务的线程池
        ScheduledExecutorService singleScheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
        //提交 3 个固定频率执行的任务
        for (int i = 0; i <3; i++) {
            //scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
            singleScheduledThreadPool.scheduleAtFixedRate(() -> {
                System.out.println(Thread.currentThread().getName());
            }, 0, 3, TimeUnit.SECONDS);
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {}
        System.out.println("10毫秒后...");
        singleScheduledThreadPool.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HphDG5W4-1603503169728)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202731999.png)]

6. newWorkStealingPool创建任务可窃取线程池,空闲线程可以窃取其他任务队列的任务

public class NewWorkStealingPoolDemo {
    public static void main(String[] args) {
        //创建 4个工作线程的 任务可窃取线程池,如果不设置并行数,默认取 CPU 总核数
        ExecutorService workStealingThreadPool = Executors.newWorkStealingPool(4);
        for (int i = 0; i <10; i++) {
            final int index = i;
            workStealingThreadPool.execute(() -> {
                try {
                    //模拟任务执行时间为 任务编号为0 1 2 的执行时间需要 3秒;其余任务200 毫秒,导致任务时间差异较大
                    if (index <= 2) {
                        Thread.sleep(1000);
                    } else {
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }

        try {
            Thread.sleep(2000);//休眠 2 秒
        } catch (InterruptedException e) {}
        System.out.println("2秒后...");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLUGorlr-1603503169730)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202942665.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程池是一种管理线程的机制,通过线程池可以有效地重用线程,避免频繁地创建和销毁线程,从而提高应用程序的性能和资源利用率。Java提供了Executor框架来实现线程池的管理。 使用线程池的步骤如下: 1. 创建线程池对象: 可以使用`Executors`类的静态方法来创建不同类型的线程池,例如`newFixedThreadPool()`、`newCachedThreadPool()`等,根据需求选择合适的线程池类型。 示例代码: ```java ExecutorService executorService = Executors.newFixedThreadPool(5); ``` 2. 提交任务给线程池执行: 使用`execute()`方法或`submit()`方法将任务提交给线程池执行。`execute()`方法用于提交一个Runnable对象,而`submit()`方法可以提交Runnable对象或Callable对象,并且可以获取任务的执行结果。 示例代码: ```java executorService.execute(new Runnable() { @Override public void run() { // 线程执行的逻辑代码 } }); ``` 3. 关闭线程池: 当不再需要使用线程池时,应该调用`shutdown()`方法来关闭线程池。这会停止接受新任务,并且等待已经提交的任务执行完成后关闭线程池。 示例代码: ```java executorService.shutdown(); ``` 通过使用线程池,可以方便地管理和控制多线程的执行。线程池会自动管理线程的创建、销毁和调度,避免了手动管理线程带来的复杂性和性能开销。同时,线程池还提供了一些附加的功能,例如任务调度、线程超时等,可以根据具体的需求进行配置和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值