深入理解java线程池

一、为什么要用线程池?(线程池的概念)

1、线程池的引入就是为了管理线程的,操作系统需要频繁的切换线程上下文,影响性能。
2、线程池其实就是线程的池子,用来帮助我们重复利用线程,避免创建大量的线程增加开销,提高响应速度。

二、线程是不是越多越好?

1、线程并不是越多越好,因为线程的创建需要占用系统内存,根据jvm规范,一个线程默认最大栈大小为1M,线程越多,会消耗很多内存。
2、如果线程的创建时间+销毁时间>执行任务的时间,就没必要创建线程。

三、线程池的创建

1、线程池推荐手动创建,一般用ThreadPoolExecutor来创建,这个类有4个构造函数
在这里插入图片描述
我们先来理解一下线程池的这些参数

  • corePoolSize:线程池的核心线程数最大值
  • maximumPoolSize:线程池最大线程数大小
  • keepAliveTime:线程池中非核心线程空闲的存活时间大小
  • unit:线程空闲存活时间单位
  • workQueue:存放任务队列的阻塞队列
  • threadFactory:用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,方便查看创建的线程
  • handler:线程池的拒绝策略,有4种类型
    a、AbortPolicy(直接抛异常,默认拒绝策略)
    b、DiscardPolicy(直接丢弃任务)
    c、DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
    d、CallerRunsPolicy(直接交给调用方处理该任务)
这里演示一下

首先手动声明一个核心线程为1,最大线程为2,阻塞队列为2的线程,拒绝策略为直接丢弃任务,这时来了5个任务,会发现第一个任务来时,核心数为1 的线程可以处理,第二个任务来时就只能放到阻塞队列里了,第三个也放到阻塞队列里,第四个任务来时阻塞队列满了,这时候会开启一个线程达到最大线程来处理,第五个任务来时,线程池处理不了,直接丢弃任务,最终执行完会执行四个任务。

package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @Author ll
 * @Description
 * @Date 1/3/22 10:00 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ThreadPoolApplicationTest {

   @Test
    public void ThreadPoolExecutorTest(){

       ThreadFactory threadFactory = new ThreadFactory() {
           private AtomicInteger idx = new AtomicInteger(1);
           private static final String THREAD_NAME_PREFIX = "mythread-pool-";
           @Override
           public Thread newThread(Runnable r) {
               return new Thread(r, THREAD_NAME_PREFIX + idx.getAndIncrement());
           }
       };

       ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
               100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
               threadFactory, new ThreadPoolExecutor.DiscardPolicy());

       Runnable runnable = () -> {
           System.out.println("当前任务被执行,线程:" + Thread.currentThread().getName() + "正在执行");
           try {
               // 等待 1s
               TimeUnit.SECONDS.sleep(1);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       };

       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);
       threadPoolExecutor.submit(runnable);

       try {
           // 等待 10s
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

   }


}

在这里插入图片描述
其它情况类似。大家可以尝试写一下。

2、 也可以用线程池工具类来创建,但是不推荐,至于为什么不推荐,下面会说,我们先来看一下这个Executors提供了哪些方法来创建线程池。
在这里插入图片描述
可以看出=提供了如下4种方式来创建不同的线程池

  • newFiexedThreadPool(int Threads):创建固定数目线程的线程池。
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:36 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class FixedThreadPoolTest {

    @Test
    public void fixedThreadPoolTest(){
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Runnable runnable = () -> {
            System.out.println("线程:" + Thread.currentThread().getName() + "正在执行");
        };
        for(int i = 0; i < 5; i++){
            executorService.submit(runnable);
        }
		executorService.shutdown();
    }

}

在这里插入图片描述

  • newSingleThreadExecutor():创建一个单线程化的Executor。
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:44 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class SingleThreadExecutorTest {

    @Test
    public void singleThreadExecutorTest(){
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for(int i = 0; i < 5; i++){
            executorService.submit(() -> {
                System.out.println("线程:" + Thread.currentThread().getName() + "正在执行");
            });
        }
        executorService.shutdown();
    }
}

在这里插入图片描述

  • newCachedThreadPool(): 创建一个可缓存的线程池,调用execute将会重用以前构造的线程(如果线程可用),如果没有可用的线程,则会创建一个新的线程并添加到池中,最后使用完会从缓存中移除那些已经超过60秒未被使用的线程。
package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 11:57 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class CachedThreadPoolTest {

    @Test
    public void cachedThreadPoolTest(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++){
            executorService.submit(()->{
                System.out.println("线程:" + Thread.currentThread().getName() + "正在执行");
            });
        }
        executorService.shutdown();
    }
}

在这里插入图片描述

  • newScheduledThreadPool(int corePoolSize): 创建一个支持定时及周期性的任务执行的线程池,多数情况下可以替代Timer类。
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author ll
 * @Description
 * @Date 1/4/22 12:00 AM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ScheduledThreadPoolTest {

    @Test
    public void scheduledThreadPoolTest() {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
        for(int i = 0; i < 5; i++){
            scheduledThreadPoolExecutor.schedule(() -> {
                System.out.println("线程:" + Thread.currentThread().getName() + "正在执行" + DateUtil.now());
            }, 1, TimeUnit.SECONDS);
        }
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务执行完毕");
        scheduledThreadPoolExecutor.shutdown();
    }
}

在这里插入图片描述

3、下面说明一下为什么不推荐使用Executors直接创建线程

  • newFiexedThreadPool(int Threads)的源码可以看出,阻塞队列没有设置大小,默认是Integer.MAX_VALUE,可以认为是无界的,最终可能会导致OOM。
    在这里插入图片描述
  • newSingleThreadExecutor()源码可以看出线程池中只有一个线程,而阻塞队列也没有指定大小,默认也是Integer.MAX_VALUE,可以认为是无界的,最终可能会导致OOM。
    在这里插入图片描述
  • newCachedThreadPool()源码可以看出,核心线程此处是0个,最大线程为Integer.MAX_VALUE,可以认为是无界大,SynchronousQueue()一个不存储元素的阻塞队列,即直接提交给线程不保持它们,可能会创建无限个线程,最终可能会导致OOM。
    在这里插入图片描述
  • newScheduledThreadPool(int corePoolSize)源码可以看出和上面类似,DelayWorkQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素,但是最大线程为Integer.MAX_VALUE,可以认为是无界大,可能会导致OOM。
    在这里插入图片描述
    上面4种Executors创建线程池使用的线程池工场也是默认的,也都不能指定拒绝策略,均使用默认的拒绝策略,即AbortPolicy

下面随便演示一个OOM

package com.threadpool;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
 * @Author ll
 * @Description
 * @Date 1/3/22 10:00 PM
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ThreadPoolApplicationTest {

    @Test
    public void test(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        int i = 0;
        while (true) {
            executorService.submit(new MyTask(i++));
        }

    }

    class MyTask implements Runnable{

        private int i;

        public MyTask(int i){
            this.i = i;
        }

        @Override
        public void run() {
            System.out.println(i);
        }
    }
}

运行结果如下
在这里插入图片描述

4、线程池中处理任务优先级

先判断核心线程是否创建满了,核心线程没满就核心线程处理,核心线程创建满了阻塞队列没满就放到阻塞队列里,阻塞队列也满了看最大线程数是否满了,最大线程数没满就创建线程处理,最大线程数也创建满了就按照自己定义的拒绝策略执行了。
在这里插入图片描述

5、线程池核心数的配置

一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,参考值可以设为 核心数+1
如果是IO密集型任务,参考值可以设置为2*核心数
Java获取线程池核心数

System.out.println(Runtime.getRuntime().availableProcessors());

在这里插入图片描述

参考链接
https://juejin.cn/post/6844903889678893063
https://juejin.cn/post/6844903560623161352
https://blog.csdn.net/xiaojin21cen/article/details/87269126
https://developer.huawei.com/consumer/cn/forum/topic/0202438478344770295

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程是Java语言中的一项非常重要的特性,它允许程序同时执行多个任务。多线程可以提高程序的并发性和性能,但是也带来了一些挑战,如线程安全、死锁、资源竞争等问题。 Java多线程的实现方式有两种:继承Thread类和实现Runnable接口。继承Thread类需要重写run()方法,该方法中包含线程需要执行的代码。实现Runnable接口需要实现run()方法,但是需要将Runnable对象传递给Thread类的构造方法中。 Java多线程的核心概念包括线程优先级、线程同步、线程通信、线程池等。线程优先级可以通过设置Thread类的setPriority()方法来进行设置,但是并不保证优先级高的线程一定会先执行。线程同步可以通过关键字synchronized来实现,它可以保证同一时刻只有一个线程可以访问共享资源。线程通信可以通过wait()、notify()、notifyAll()等方法来实现,它可以使线程之间进行协作。线程池可以通过Executor框架来实现,它可以实现线程的复用,减少线程创建和销毁的开销。 在使用Java多线程时,需要避免一些常见的问题,如死锁、资源竞争、线程安全等。死锁会导致线程之间相互等待,无法进行下去;资源竞争会导致多个线程同时访问共享资源,可能会导致数据的不一致;线程安全问题会导致多个线程同时访问共享资源,可能会导致数据的不一致或者程序崩溃等问题。 综上所述,Java多线程是一项非常重要的特性,它可以提高程序的并发性和性能,但是在使用时需要注意一些常见的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值