首页 > 其他分享 >线程池的创建方式

线程池的创建方式

时间:2023-03-25 19:23:34浏览次数:32  
标签:方式 Executors int 创建 任务 线程 执行

1.什么是线程池

随着多线程的大量使用,伴随着大量的线程创建与销毁等这些开销,为了减少这些开销,进行管理线程,线程池就应运而生了。因此线程池是一种基于池化思想管理和使用线程的机制,主要是为了方便管理线程,减少线程的频繁创建与销毁而浪费的资源。

2.线程池的使用

2.1 线程池的创建
线程池的创建方式按照其种类,大致可以分为两大类,分别是Executors与ThreadPoolExecutor。而这两大类又可以分为七个小类,其中Executors有六种,ThreadPoolExecutor有一种,分别如下:
Executors:

创建方式 方式说明
Executors.newFixedThreadPool 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待
Executors.newCachedThreadPool 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,如果线程数量不够,则创建新的线程,直到达到所设置的线程上限
Executors.newSingleThreadExecutor 船舰单个线程的线程池,可以保证任务按照顺序执行
Executors.newSingleThreadScheduledExecutor 创建一个单线程的可以延迟执行任务的线程池
Executors.newScheduledThreadPool 创建一个可以执行延迟任务的线程池
Executors.newWorkStealingPool 创建一个抢占式执行的线程池(任务执行顺序不确定)

ThreadPoolExecutor:

创建方式 方式说明
ThreadPoolExecutor() 这个方法是最原始的创建线程池的方法,其有七个参数,每个参数所代表的意义在下面会详细说明

2.2 各种线程池创建的举例说明
(1)Executors.newFixedThreadPool()
Executors.newFixedThreadPool()其共有两种构造方式,一种为newFixedThreadPool(int nThreads),其中的参数nThreads表示线程的数量。另一种为newFixedThreadPool(int nThreads, ThreadFactory threadFactory),其相比前一种多了个threadFactory,这个参数是线程的创建方式。这里的例子用的是第一种的线程池创建方式,线程的创建使用它的默认方式。如下:

public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(4);

        for (int i = 0; i < 10; i++) {
            int j = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程名为:" + Thread.currentThread().getName());
                }
            });
        }

    }

执行结果:
image
分析:由执行结果可以看出,这边线程池执行任务,创建的线程为4个,达到了4个之后就不会创建新的线程了,会服用前面空闲下来的线程来继续执行新的任务。
PS:需要注意的是代码中的Executor.execute()方法,是线程池执行任务的方法,另外执行任务的方法还有另一个方法为ExecutorService.submit()方法,虽然这两个方法都是线程池执行任务的方法,不过他们也是有区别的,execute()的方法执行执行Runnable的任务,submit()方法除了能执行Runnable的任务,还能够执行Callable的任务,也就是submit()方法能够执行有返回值的任务,而execute()方法则不行。
(2)Executors.newCachedThreadPool()
Executors.newCachedThreadPool()方法同样有两种构造方式,一种为newCachedThreadPool(),这种方式没有参数,另一种为newCachedThreadPool(ThreadFactory threadFactory),这种相比前一种同样是多了一个线程创建方式的参数。这里同样是以第一种为例,如下:

public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int j = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程的名称:" + Thread.currentThread().getName());
                }
            });
        }
    }

执行结果:
循环十次:
image
循环一百次:
image
分析:由上面循环十次的执行结果和循环一百次的执行结果可以看出其线程是有缓存的,当来个新的任务是,如果当前没有空闲的线程,则会创建新的线程来执行新任务,如果此时有空闲的线程,则会使用这个空闲的线程来执行任务。另外当这个线程池中的任务都处于空闲,并达到一定的时间时,则会将该线程池回收,因此这就是这个线程池的程序是会结束,而其它线程池的程序不会结束的原因,另外,线程的空闲时间默认为60秒。
(3)Executors.newSingleThreadExecutor()
Executors.newSingleThreadExecutor()方法同样有两种构造方式,一种为newSingleThreadExecutor(),这种方式没有参数,另一种为newSingleThreadExecutor(ThreadFactory threadFactory),这种相比前一种同样是多了一个线程创建方式的参数。这里同样是以第一种为例,如下:

public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int j = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程名为:" + Thread.currentThread().getName());
                }
            });
        }
    }

执行结果:
image
分析:由上面的结果可以看出该线程池只会有一个线程来进行执行任务,而执行任务的顺序是有序的,会按照先进先出的顺序来进行执行。这里虽然线程池只有一个线程和创建线程数量一样,但是其避免了频繁创建销毁线程的开销,实现了这一个线程的复用。
(4)Executors.newSingleThreadScheduledExecutor()
Executors.newSingleThreadScheduledExecutor()方法同样有两种构造方式,一种为newSingleThreadScheduledExecutor(),这种方式没有参数,另一种为newSingleThreadScheduledExecutor(ThreadFactory threadFactory),这种相比前一种同样是多了一个线程创建方式的参数。这里同样是以第一种为例,如下:

public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 10; i++) {
            int j = i;
            System.out.println("第" + j + "个任务,加入时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            executor.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程名为:" + Thread.currentThread().getName() + ",执行时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                }
            }, 5, TimeUnit.SECONDS);
        }
    }

执行结果:
image
分析:由上面的执行结果可以得出其确实实现了定时执行任务的功能,能够发现,任务的执行时间比任务的加入晚了五秒钟,并且每个任务都是如此。
PS:其中的ScheduledExecutorService.schedule()方法是具有定时执行功能的线程池特有的方法,表示延迟执行一次定时任务。另外除了这个之外其还有另外两种,分别是ScheduledExecutorService.scheduleAtFixedRate()ScheduledExecutorService.scheduleWithFixedDelay()。其中ScheduledExecutorService.scheduleAtFixedRate()表示固定频率执行,以上次任务的开始时间作为计算时间执行。而ScheduledExecutorService.scheduleWithFixedDelay()表示固定频率执行但是以上次人物的结束时间为计算时间执行。
(5)Executors.newScheduledThreadPool()
Executors.newScheduledThreadPool()方法同样有两种构造方式,一种为newScheduledThreadPool(int corePoolSize),这种方式只有一个参数表示会创建的线程数量,另一种为newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory),这种相比前一种同样是多了一个线程创建方式的参数。这里同样是以第一种为例,如下:


// schedule方式
public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

        for (int i = 0; i < 10; i++) {
            int j = i;
            System.out.println("第" + j + "个任务,加入时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            executor.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程名称为:" + Thread.currentThread().getName() + ",当前执行时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                }
            }, 5, TimeUnit.SECONDS);
        }

    }
	
	
// scheduleAtFixedRate方式
public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

        // 以固定频率执行任务,其中initialDelay表示第一次执行与加入时间间隔多久,period表示两次执行之间的间隔时间为多少,也就是频率。
        // 需要注意的是如果线程有沉睡,则看沉睡时间与间隔时间哪个比较长,按照长的算,如果想让执行间隔时间按照睡眠时间加上间隔时间则使用executor.scheduleWithFixedDelay()方法
        System.out.println("加入时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程名称为:" + Thread.currentThread().getName() + ",当前执行时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                try {
                    Thread.sleep(3*1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, 3, 5, TimeUnit.SECONDS);

    }
	
	
// scheduleWithFixedDelay方式
public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

        // 以固定频率执行任务,其中initialDelay表示第一次执行与加入时间间隔多久,period表示两次执行之间的间隔时间为多少,也就是频率。
        // 执行间隔时间按照睡眠时间加上间隔时间
        System.out.println("加入时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程名称为:" + Thread.currentThread().getName() + ",当前执行时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                try {
                    Thread.sleep(3*1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, 3, 5, TimeUnit.SECONDS);

    }

执行结果:
1.schedule方式
image
2.scheduleAtFixedRate方式
image
3.scheduleWithFixedDelay方式
image
分析:由上面可以看出这边是多个线程来执行延迟定时任务,以及三种定时任务的执行方式之间的区别从上面的执行结果中也能够看出。
(6)Executors.newWorkStealingPool()
Executors.newWorkStealingPool()方法同样有两种构造方式,一种为newWorkStealingPool(),这种方式没有参数,另一种为newWorkStealingPool(int parallelism),这种相比前一种多了并行度参数。这里同样是以第一种为例,如下:

public static void main(String[] args) {
        ExecutorService executor = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            int j = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + j + "个任务,当前线程名为:" + Thread.currentThread().getName());
                }
            });
        }
    }

执行结果:
image
(7)ThreadPoolExecutor()
ThreadPoolExecutor()它的构造方法会比较多,不过看它里面的各个构造方法最后都会调用那个有所有参数的构造函数,也就是这个:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

参数说明:
corePoolSize:这个表示核心线程的数量。
maximumPoolSize:这个表示最大线程数量。
keepAliveTime:这个表示空闲线程存活时间。
unit:这个是存活时间的单位。
workQueue:这个表示工作队列,是当核心线程满了之后会存放在这里。
threadFactory:这个是线程的创建方式。
handler:这个表示拒绝策略,其有jdk提供的四种拒绝策略和一种自定义的拒绝策略。
拒绝策略:

名字 描述
AbortPolicy 如果线程池拒绝了任务,直接报错。
CallerRunsPolicy 线程池让调用者去执行
DiscardPolicy 如果线程池拒绝了任务,直接丢弃。
DiscardOldestPolicy 如果线程池拒绝了任务,直接将工作队列中第一个任务给丢弃,将新任务入队。
自定义拒绝策略 实现RejectedExecutionHandler接口

流程大致说明:
当有任务进来时首先会判断当前正在执行的任务数是否小于核心线程数,如果小于核心线程数,则会调用核心线程来进行执行新任务,如果不小于则会进行判断当前工作队列是否已满,如果未满,则将任务放到任务队列中,如果已满,则会接着判断当前正在执行的任务数量是否小于最大线程数,如果小于最大线程数则会创建一个新的线程来执行该任务,如果不小于,最会按照拒绝策略决绝该任务。

public class TestMyThread {

    public static void main(String[] args) {
        TestMyThread testMyThread = new TestMyThread();
        testMyThread.test();
    }

    private void test() {
        ThreadFactory threadFactory = new MyThreadFactory("交易核心线程");

        Executor executor = new ThreadPoolExecutor(3,
                6,
                30,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(11),
                threadFactory,
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i <= 20; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }

    }

    class MyThreadFactory implements ThreadFactory {

        private final String namePrefix;
        private final AtomicInteger nextId = new AtomicInteger(1);

        MyThreadFactory(String featureOfGroup) {
            namePrefix = featureOfGroup + ",线程编号:";
        }


        /**
         * Constructs a new {@code Thread}.  Implementations may also initialize
         * priority, name, daemon status, {@code ThreadGroup}, etc.
         *
         * @param r a runnable to be executed by new thread instance
         * @return constructed thread, or {@code null} if the request to
         * create a thread is rejected
         */
        @Override
        public Thread newThread(Runnable r) {
            String name = namePrefix + nextId.getAndIncrement();
            return new Thread(null, r, name, 0);
        }
    }

}

执行结果:
image
分析:
由执行结果可知其创建的线程数量最大为6个,然后在执行了17条任务之后,触发了异常,从异常可以看出其是触发了拒绝策略。

3.总结

其实Executors创建线程池的方法,里面距离创建线程池的方法也是调用ThreadPoolExecutor来实现的,所以还是比较建议使用ThreadPoolExecutor这种方式来创建线程池。

标签:方式,Executors,int,创建,任务,线程,执行
From: https://www.cnblogs.com/mcj123/p/17239951.html

相关文章

  • API Hook检测方式
    APIHook:在Windows系统中,大量的功能都是通过系统API提供的。APIHook技术就是拦截API调用,从而实现对程序的控制。APIHook技术可以通过修改IAT表来实现。IAT(ImportAddres......
  • Java多线程
    一、多线程简介 Java多线程是指在一个程序中同时执行多个线程(线程就是一条执行路径)。Java中的多线程可以提高程序的运行效率和并发性,通常用于执行一些耗时的操作或需要同......
  • jQuery多种请求方式
    一、请求方式$.ajax():最常用的发起HTTP请求的方法之一,可以自定义请求头、请求体等参数,支持异步和同步请求。$.ajax({type:"GET",url:"http://example.com/data......
  • 如何使用Photino创建Blazor项目进行跨平台
    Photino是什么Photino是一组使用Web(HTML/CSS/JavaScript)UI创建桌面应用程序的技术。TryPhotino.io维护.NET构建,并鼓励社区开发Photino.Native控件以用于其他语言和......
  • 如何在Android Studio中创建自定义图标
    今天学习时发现了一个很棒的功能,那就是在AndroidStudio中创建自定义图标,我们在开发app时,常需要用到一些图标,我们当然可以去网上公共素材库找,但是大小什么的往往不尽......
  • Java使用IntelliJ IDEA创建控制台程序并通过JDBC连接到数据库
    1、创建一个java控制台程序并测试首先,直接新建一个默认的空的Java模块即可,随便取个名字在src目录下右键->新建->创建一个包,随便取个名字在包中创建一个Test类,写个helloworld......
  • Java使用IntelliJ IDEA创建一个基于Swing的GUI图形化程序,打包发布为jar
    1、创建GUI窗体首先,直接新建一个默认的空的Java模块即可,随便取个名字之后再src目录下右键,新建,创建一个Swing的GUI窗体,随便取个名字给主窗体改个名字到java代码中生成一个窗......
  • 增加单条(判断数据是字典=单条),增加多条(判断数据是字典=列表套字典),修改单条,修改多条
    1.增加单条或多条数据判断是否是dict或list:  2.修改单条数据    3.继承ListSerializer,根据list_serializer_class,重写ListSerializer中的方法  4.......
  • 获得class类的几种方式
    packageedu.wtbu;publicclassDemo01{publicstaticvoidmain(String[]args)throwsClassNotFoundException{Personstudent=newStudent();......
  • flask1:使用pipenv创建虚拟环境
    首先理解pipenv和虚拟环境。虚拟环境可以理解成为独立的容器,可以把特定版本的python和特定版本相关安装包(flask)都安装在这个容器里,容器种的代码只能调用这些安装包。pipe......