首页 > 编程语言 >Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例

Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例

时间:2023-04-10 20:36:29浏览次数:45  
标签:task 示例 创建 启动 System 任务 线程 执行

场景

Java中创建线程的方式有三种

1、通过继承Thread类来创建线程

定义一个线程类使其继承Thread类,并重写其中的run方法,run方法内部就是线程要完成的任务,

因此run方法也被称为执行体,使用start方法来启动线程。

2、通过实现Runanle接口来创建线程

首先定义Runnable接口,并重写Runnable接口的run方法,run方法的方法体同样是该线程的线程执行体。

3、通过Callable 和 Future来创建线程

Runnable接口执行的是独立的任务,Runnable接口不会产生任何返回值,

如果希望在任务完成之后能够返回一个值的话,可以实现Callable接口。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

Java创建线程的三种方式

1、通过继承Thread类来创建线程

public class TJavaThread extends Thread{
    static int count;

    @Override
    public synchronized void run() {
        for(int i =0;i<10000;i++){
            count++;
        }
    }

    public static void main(String[] args) {
        TJavaThread tJavaThread = new TJavaThread();
        tJavaThread.start();
        try {
            //使用线程的join方法,用来等待线程的执行结束,如果不加join方法,它就不会等待tJavaThread的执行完毕。
            tJavaThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
}

2、通过实现Runanle接口来创建线程

public class TJavaThreadRunable implements Runnable{

    static int count;

    @Override
    public synchronized void run() {
        for(int i=0;i<10000;i++){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new TJavaThreadRunable());
        thread.start();
        thread.join();
        System.out.println(count);
    }
}

3、通过Callable 和 Future来创建线程

public class TJavaThreadCallable implements Callable {

    static int count;
    public TJavaThreadCallable(int count){
        this.count = count;
    }

    @Override
    public Object call(){
        return count;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask((Callable<Integer>)()->{
            for (int i =0;i<1000;i++){
                count++;
            }
            return count;
        });
        Thread thread = new Thread(task);
        thread.start();

        Integer total = task.get();
        System.out.println(total);
    }
}

Java使用线程池来创建线程

Executor虽然不是传统线程创建的方式之一,但是它却成为了创建线程的替代者,使用线程池的好处
1、利用线程池能够复用线程、控制最大并发数
2、实现任务线程队列缓存策略和拒绝机制
3、实现某些与时间相关的功能,如定时执行、周期执行等。
4、隔离线程环境。比如两个服务在同一台服务器上,分别开启两个线程池,避免各服务线程相互影响。

ExecutorService是Executor的默认实现,也是Executor的扩展接口,

ThreadPoolExecutor类提供了线程池的扩展实现。Executors类为这些Executor提供了方便的工厂方法。

ExecutorService创建线程的几种方式:

1、CacheedThreadPool

创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。

如果现有线程没有可用的,则创建一个新线程并添加到池中。

终止并从缓存中移除那些已有 60 秒钟未被使用的线程。CacheThreadPool会为每一个任务都创建一个线程

 

   private static void CacheedThreadPoolTest() {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            //submit()有返回值,而execute()没有
            //submit()可以进行Exception处理
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
            });
        }
        service.shutdown();
    }

2、FixedThreadPool

使你可以使用有限的线程集来启动多线程,

可以一次性的预先执行高昂的线程分配,因此也就可以限制线程的数量。

这样可以节省时间,因为你不必为每个任务都固定的付出创建线程的开销。

    private static void FixedThreadPoolTest() {
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            int k = i;
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
                System.out.println(Thread.currentThread().getId() + "--" + k);
            });
        }
        //ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
        //这个线程在Executor中所有任务完成后退出、
        service.shutdown();
    }

3、SingleThreadExecutor

就是线程数量为1的FixedThreadPool,如果向SingleThreadPool一次性提交了多个任务,

那么这些任务将会排队。每个任务都会在下一个任务开始前结束,所有的任务都将使用相同的线程。

SingleThreadPool会序列化所有提交给他的任务,并会维护它自己的悬挂队列。

从输出结果来看,任务都是挨着进行的。为任务分配五个线程,但是这五个线程不像上面有换进换出的效果,

它每次都会先执行完自己的那个线程,然后余下的线程继续走完这条线程的执行路径。

可以使用SingleThreadExecutor来确保任意时刻都只有唯一一个任务在运行。

    private static void SingleThreadExecutorTest() {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            int k = i;
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
                System.out.println(Thread.currentThread().getId() + "--" + k);
            });
        }
        //ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
        //这个线程在Executor中所有任务完成后退出、
        service.shutdown();
    }

4、ScheduledThreadPool

常用于需要延迟执行或周期循环执行任务的场景

schedule()方法可以用来延迟任务的执行

运行下面任务,则先输出时间,延迟2秒后才执行

    private static void ScheduledThreadPoolTestSchedule() {
        System.out.println("当前时间:" + System.currentTimeMillis());
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        service.schedule(
                () ->
                System.out.println("开始执行:" + System.currentTimeMillis()), 2, TimeUnit.SECONDS);
        service.shutdown();
    }

scheduleAtFixedRate()方法 固定频率执行方法

    private static void ScheduledThreadPoolTestFixedRate(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },2,1,TimeUnit.SECONDS);
    }

scheduleWithFixedDelay  固定的间隔时间执行任务

    private static void ScheduledThreadPoolTestFixedDelay(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },2,1,TimeUnit.SECONDS);
    }

scheduleAtFixedRate与scheduleWithFixedDelay区别?

scheduleAtFixedRate的下一次执行时间是上一次执行时间+间隔时间

scheduleWithFixedDelay下一次执行时间是上一次执行时间结束时系统时间+间隔时间

scheduleAtFixedRate执行结果

scheduleWithFixedDelay执行结果

5、newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;

    private static void SingleThreadScheduleTest(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
            }
        },2,1,TimeUnit.SECONDS);
    }

6、newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)

又称任务窃取线程池,可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,

实际的线程数可能会动态增长和收缩,不能保证提交任务的执行顺序。

以下为设置线程数为4

    private static void WorkStealingPoolTest(){
        ExecutorService executorService = Executors.newWorkStealingPool(4);
        for (int i =0;i<10;i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"开始执行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name+"执行结束");
                }
            });
        }
        System.out.println("cpu核心数:"+ Runtime.getRuntime().availableProcessors());
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

运行效果

 

 

这里的cpu核心数为8,如果不设置线程数则直接

ExecutorService executorService = Executors.newWorkStealingPool();

此时执行结果

 

 

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式

《阿里巴巴JAVA开发手册》有这样一条强制规定:

线程池不允许使用Executors去创建,而应该通过ThreadPoolExecutor方式,这样处理方式更加明确线程池运行规则,

规避资源耗尽风险。

说明: Executors 返回的线程池对象的弊端如下:
 (1) FixedThreadPool 和 SingleThreadPool :
    允许的请求队列的长度可能会堆积大量的请求,从而导致 OOM。
 (2) CachedThreadPool :
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

ThreadPoolExecutor参数说明

        corePoolSize - 线程池核心线程数量
        maximumPoolSize - 线程池最大数量
        keepAliveTime - 空闲线程存活时间
        unit - 时间单位
        workQuene - 线程池中所使用的缓冲队列
        handler - 线程池对拒绝任务的处理策略

ThreadPoolExecutor执行流程

 

 

示例代码

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.AbortPolicy());
        for(int i =1;i<=7;i++){
            String task = "task:"+i;
            threadPoolExecutor.execute(new ThreadPoolTask(task));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

任务具体实现类

    static class ThreadPoolTask implements Runnable{

        private String taskName;

        ThreadPoolTask(String task){
            this.taskName = task;
        }

        @Override
        public void run() {
            System.out.println("启动:"+taskName);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这里假设每个任务需要执行10秒,每隔0.5秒执行一个任务。

当循环数为7,即不大于最大线程数+队列大小时,执行结果如下
    启动:task:1
    启动:task:2
    启动:task:6
    启动:task:7
    启动:task:3
    启动:task:4
    启动:task:5

 

 

执行结果分析:

提交1、2两个任务,判断小于corePoolSize,会为每一个任务创建一个线程

提交3、4、5三个任务时,判断正在执行的任务数量为2,且每个任务执行时间为10s,所以会将这三个放入到workQueue中等待执行

提交6、7两个任务时,因为workQuene队列的大小为3,此时workQueue队列中存储的任务数量满了,

会判断当前线程池中正在执行的任务是否小于maximumPoolSize,这里是4,

如果小于4则创建新的线程来执行任务6和7,此时7个任务都提交完毕,那么等待任务3、4、5会在前面每个10s的任务执行完之后执行。

当修改循环数为10

    启动:task:1
    启动:task:2
    启动:task:6
    启动:task:7
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.ruoyi.demo.thread.threadpool.ThreadPool$ThreadPoolTask@deb6432 rejected from java.util.concurrent.ThreadPoolExecutor@28ba21f3[Running, pool size = 4, active threads = 4, queued tasks = 3, completed tasks = 0]
     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
     at com.ruoyi.demo.thread.threadpool.ThreadPool.ThreadPoolExecutorTest(ThreadPool.java:230)
     at com.ruoyi.demo.thread.threadpool.ThreadPool.main(ThreadPool.java:280)
    启动:task:3
    启动:task:4
    启动:task:5

   

 

 

执行结果分析,当执行第8个任务时,判断执行线程总数大于最大线程数+队列大小,直接执行拒绝策略,同理任务9和10也是如此。
    这里的拒绝策略是AbortPolicy

拒绝策略

1、AbortPolicy - 丢弃任务并抛出RejectedExecutionException异常

2、CallerRunsPolicy - 将被拒绝的任务添加到线程池正在运行的线程中去执行

将上面修改之后的执行结果

    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:8
    //启动:task:3
    //启动:task:4
    //启动:task:5
    //启动:task:9
    //启动:task:10

3、DiscardPolicy - 丢弃任务,但是不抛出异常
    //此时执行结果如下
    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:3
    //启动:task:4
    //启动:task:5

4、DiscardOldestPolicy - 丢弃队列最前面的任务,然后重新尝试执行任务
    //此时执行结果如下
    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:8
    //启动:task:9
    //启动:task:10

 

标签:task,示例,创建,启动,System,任务,线程,执行
From: https://www.cnblogs.com/badaoliumangqizhi/p/17304186.html

相关文章

  • ROS系统(3)创建工作空间
    在主文件夹中打开终端,输入命令:mkdir-pcatkin_ws1/src  在主文件夹中就创建了一个名为catkin_ws1的文件夹,打开ws1文件夹里面还有一个名为src的文件夹  通过命令初始化创建工作空间,进入到catkin_ws1/src中(输入命令:cdcatkin_ws1/src/)输入命令catkin_init_workspace......
  • Jmeter线程组间传递变量
    做接口测试,上一个线程组(A线程组)提取的变量,需要传递给下一个线程组(B线程组)使用。故需要将A线程组内提取的变量设置为全局变量。实现如下:1.json提取变量(A线程组)通过json提取器,将A线程组请求中的billId提取出来,如下:2. BeanShell取样器定义变量(A线程组)添加【BeanShell......
  • flask框架05 信号 flask-script命令 sqlalchemy创建操作数据表
    今日内容详细目录今日内容详细1信号1.1django信号2flask-script3sqlalchemy快速使用4sqlalchemy介绍和快速使用4.1sqlalchemy介绍和快速使用5创建操作数据表1信号#Flask框架中的信号基于blinker(安装这个模块),其主要就是让开发者可是在flask请求过程中定制一些用户行为......
  • 执行redis-cli命令创建redis集群时报错“Could not connect to Redis at IP:端口: No
    问题描述:执行redis-cli命令创建redis集群时报错“CouldnotconnecttoRedisatIP:端口:Noroutetohost”,如下所示:数据库:redis6.2.6系统:rhel7.91、异常重现[root@leo-redis626-asrc]#/usr/local/src/redis-6.2.6/src/redis-cli--clustercreate--cluster-replicas1......
  • 线程和队列应用--消费者和生产者
    1、用一个队列存储商品2、创建一个专门生产商品的线程类,当商品数量少于50时,开始生产商品,每次生产200个商品,每生产一轮,暂停1s3、创建一个专门消费商品的线程类,当商品数量大于10时就开始消费,循环消费,每次消费3个,当商品数量少于10的时候,暂停2s    ......
  • 翻译文本 API说明示例
    t_text-翻译文本名称 类型 必须 描述key String 是 调用key(必须以GET方式拼接在URL中)secret String 是 调用密钥(获取key和secret)api_name String 是 API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cache String 否 [yes,no]默认yes,将调用缓存的数据,速度比......
  • flask-day6——sqlalchemy快速插入数据、scoped_session线程安全、sqlalchemy基本增删
    目录一、sqlalchemy快速插入数据二、scoped_session线程安全2.1基本使用2.2加在类上的装饰器三、基本增删查改3.1基本增删查改和高级查询3.2原生sql3.3django中执行原生sql四、一对多4.1表模型4.2新增和基于对象的查询五、多对多5.1表模型5.2增加和基于对象的跨表查询六......
  • 信号、flask-script、sqlalchemy 快速使用、sqlalchemy介绍和快速使用、创建操作数据
    目录1信号1.2django信号2flask-script3sqlalchemy快速使用4sqlalchemy介绍和快速使用4.1原生操作的快速使用5创建操作数据表1信号#Flask框架中的信号基于blinker(安装这个模块),其主要就是让开发者可是在flask请求过程中定制一些用户行为flask和django都有#观察者模......
  • sqlalchemy快速插入数据、scoped_session线程安全、基本增删查改、一对多、 多对多、
    目录1sqlalchemy快速插入数据2scoped_session线程安全2.1基本使用2.2加在类上的装饰器3基本增删查改3.1基本增删查改和高级查询3.2原生sql3.3django中执行原生sql4一对多4.1表模型4.2新增和基于对象的查询5多对多5.1表模型5.2增加和基于对象的跨表查询6连表查询1......
  • Dart内存泄漏示例及如何解决
    内存泄漏是指应用程序中的对象被分配了内存空间,但在不再需要这些对象时,它们仍然占用着内存空间而没有被垃圾回收。Dart语言使用自动垃圾回收器来管理内存,但如果代码存在一些常见的陷阱,可能会导致内存泄漏问题。下面是一些解决方案:及时释放资源:在使用完资源后,及时将其关闭或释放。例......