线程的创建(Runnable,Future,CompletionService,CompletableFuture的辨析)

news2024/11/19 7:35:05

直接使用Thread

直接让某个类继承Thread类,复写run方法,外部调用的时候直接调用start方法。
因为java的单继承模式,但是我们一般不直接使用这种方法。

使用Runnable

@Slf4j
public class MyTask implements Runnable {

    @Override
    public void run() {
        log.info("i love you");
    }

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        Thread myThread = new Thread(myTask);
        myThread.start();
    }
}

代码如上,另外 使用匿名Runnable其实和上面也是一直大的思路
使用runnable解决java单继承的问题,但是也还有一个问题,那就是,没有返回值。

使用Callable与FutureTask

@Slf4j
public class TestFutureTask {

    public static void main(String[] args) {

        Callable<Integer> callable = () -> {
            int a = new Random().nextInt(100);
            Thread.sleep(1000);
            log.info(a + "   in subThread " + System.currentTimeMillis());
            return a;
        };

        FutureTask<Integer> ft = new FutureTask<>(callable);
        log.info(" main start " + System.currentTimeMillis());
        ft.run();

        try {
            log.info("in mainThread " + System.currentTimeMillis());
            log.info("得到结果 " + ft.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

代码如上,使用FutureTask来承接Callable,Callable里面存放需要执行的任务

另外如果future的结果还没有出来,调用它的get方法就会阻塞
那如果我就是想立即知道future到底有没有"计算"出结果呢?
在这里插入图片描述用isDone()
带参数的get,就是给个时间限制,如果再n个单位时间内,还没有获得结果,就抛出异常。
刚才的代码执行结果如下:

17:20:39.838 [main] INFO com.alibaba.TestFutureTask -  main start 1685697639835
17:20:40.844 [main] INFO com.alibaba.TestFutureTask - 25   in subThread 1685697640844
17:20:40.844 [main] INFO com.alibaba.TestFutureTask - in mainThread 1685697640844
17:20:40.844 [main] INFO com.alibaba.TestFutureTask - 得到结果 25

我们可以从主线程里拿到Callable任务的返回值
不过,大家应该注意到一个问题:
打印了main start后过了很久in mainThread才打印,也就是说
FutureTask的run方法会阻塞主进程!!
那应该怎么办呢?

        FutureTask<Integer> ft2 = new FutureTask<>(callable);
        log.info(" main start1 " + System.currentTimeMillis());
        new Thread(ft2).start();
        try {
            log.info("in mainThread " + System.currentTimeMillis());
            log.info("得到结果2 " + ft2.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

如上把原来的ft.run();改成new Thread(ft).start()

使用线程池来执行任务

上面三种启动线程的方式,都只是用来学习的,并不推荐,因为每个任务都启动一个线程去执行,实在是太慢了,我们一般都会使用线程池。
例如:

       int  maximumPoolSize =Runtime.getRuntime().availableProcessors() * 4 +1;
        int corePoolSize = maximumPoolSize/2;
    ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,3,TimeUnit.SECONDS
            , new LinkedBlockingQueue<>(1024), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        executor.allowCoreThreadTimeOut(true);

CompletionService

CompletionService干什么的?可以从线程池里拿到最先完成的 任务

package com.alibaba.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

import lombok.extern.slf4j.Slf4j;

/**
 * @author zjhua
 * @description
 * @date 2020/1/28 21:07
 */
@Slf4j
public class CompletionServiceTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testFuture();
    }


    // 结果的输出和线程的放入顺序 有关(如果前面的没完成,就算后面的哪个完成了也得等到你的牌号才能输出!),so阻塞耗时
    public static void testFuture() throws InterruptedException, ExecutionException {
        long beg = System.currentTimeMillis();
        log.info("testFuture()开始执行:" + beg);
        ExecutorService executor = Executors.newCachedThreadPool();
        List<Future<String>> result = new ArrayList<>();
        for (int i = 5; i > 0; i--) {
            Future<String> submit = executor.submit(new Task(i));
            result.add(submit);
        }
        executor.shutdown();
        for (int i = 0; i < 5; i++) {// 一个一个等待返回结果
            Thread.sleep(500);
            log.info("线程" + i + "执行完成:" + result.get(i).get());
        }
        log.info("testFuture()执行完成:" + System.currentTimeMillis() + "," + (System.currentTimeMillis() - beg));
    }


    private static class Task implements Callable<String> {

        private volatile int i;

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

        @Override
        public String call() throws Exception {
            long beg = System.currentTimeMillis();
            Thread.sleep(i * 500);
            long end = System.currentTimeMillis();
            return "任务 : " + i + "beg: " + beg + " consume:" + (end - beg);
        }

    }
}

大家先看看testFuture方法
我们把任务交给了线程池之后,拿到了一堆Future
List<Future> result = new ArrayList<Future>();
可是我压根不知道哪个先完成了,只能按照放入的顺序遍历get了。
上面打印的日志是:

2023-06-03 00:05:00.557 INFO  [main] c.a.t.CompletionServiceTest - testFuture()开始执行:1685721900556
2023-06-03 00:05:03.073 INFO  [main] c.a.t.CompletionServiceTest - 线程0执行完成:任务 : 5beg: 1685721900560 consume:2513
2023-06-03 00:05:03.583 INFO  [main] c.a.t.CompletionServiceTest - 线程1执行完成:任务 : 4beg: 1685721900561 consume:2003
2023-06-03 00:05:04.088 INFO  [main] c.a.t.CompletionServiceTest - 线程2执行完成:任务 : 3beg: 1685721900561 consume:1513
2023-06-03 00:05:04.594 INFO  [main] c.a.t.CompletionServiceTest - 线程3执行完成:任务 : 2beg: 1685721900561 consume:1006
2023-06-03 00:05:05.101 INFO  [main] c.a.t.CompletionServiceTest - 线程4执行完成:任务 : 1beg: 1685721900561 consume:514
2023-06-03 00:05:05.101 INFO  [main] c.a.t.CompletionServiceTest - testFuture()执行完成:1685721905101,4545

第一个提交的任务,其实执行的耗时最长。
其实的目的就是,拿到最先执行完成的那个。
ok 看下面的代码

 // 结果的输出和线程的放入顺序 无关(谁完成了谁就先输出!主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序),so很大大缩短等待时间
    private static void testCompletionService() throws InterruptedException, ExecutionException {
        long beg = System.currentTimeMillis();
        log.info("testCompletionService()开始执行:" + beg);
        ExecutorService executor = Executors.newCachedThreadPool();
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executor);
        for (int i = 5; i > 0; i--) {
            completionService.submit(new Task(i));
        }
        executor.shutdown();
        for (int i = 0; i < 5; i++) {
            // 检索并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
            Future<String> future = completionService.take(); // 这一行没有完成的任务就阻塞
            Thread.sleep(500);
            log.info("线程" + i + "执行完成:" + future.get()); // 这一行在这里不会阻塞,引入放入队列中的都是已经完成的任务
        }
        log.info(
            "testCompletionService()执行完成:" + System.currentTimeMillis() + "," + (System.currentTimeMillis() - beg));
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testFuture();
        log.info("************");
        testCompletionService();
    }

这样最后的结果就是

2023-06-03 00:10:09.915 INFO  [main] c.a.t.CompletionServiceTest - testFuture()开始执行:1685722209913
2023-06-03 00:10:12.423 INFO  [main] c.a.t.CompletionServiceTest - 线程0执行完成:任务 : 5beg: 1685722209918 consume:2505
2023-06-03 00:10:12.923 INFO  [main] c.a.t.CompletionServiceTest - 线程1执行完成:任务 : 4beg: 1685722209918 consume:2006
2023-06-03 00:10:13.437 INFO  [main] c.a.t.CompletionServiceTest - 线程2执行完成:任务 : 3beg: 1685722209918 consume:1505
2023-06-03 00:10:13.951 INFO  [main] c.a.t.CompletionServiceTest - 线程3执行完成:任务 : 2beg: 1685722209918 consume:1004
2023-06-03 00:10:14.465 INFO  [main] c.a.t.CompletionServiceTest - 线程4执行完成:任务 : 1beg: 1685722209918 consume:504
2023-06-03 00:10:14.465 INFO  [main] c.a.t.CompletionServiceTest - testFuture()执行完成:1685722214465,4552
2023-06-03 00:10:14.465 INFO  [main] c.a.t.CompletionServiceTest - ************
2023-06-03 00:10:14.465 INFO  [main] c.a.t.CompletionServiceTest - testCompletionService()开始执行:1685722214465
2023-06-03 00:10:15.476 INFO  [main] c.a.t.CompletionServiceTest - 线程0执行完成:任务 : 1beg: 1685722214466 consume:504
2023-06-03 00:10:15.982 INFO  [main] c.a.t.CompletionServiceTest - 线程1执行完成:任务 : 2beg: 1685722214466 consume:1010
2023-06-03 00:10:16.482 INFO  [main] c.a.t.CompletionServiceTest - 线程2执行完成:任务 : 3beg: 1685722214466 consume:1501
2023-06-03 00:10:16.983 INFO  [main] c.a.t.CompletionServiceTest - 线程3执行完成:任务 : 4beg: 1685722214466 consume:2001
2023-06-03 00:10:17.487 INFO  [main] c.a.t.CompletionServiceTest - 线程4执行完成:任务 : 5beg: 1685722214466 consume:2502
2023-06-03 00:10:17.487 INFO  [main] c.a.t.CompletionServiceTest - testCompletionService()执行完成:1685722217487,3022

testCompletionService整体耗时3022毫秒,之前的testFuture是4000多毫秒。
为什么会有这样的差别呢?
核心就是

//检索并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
Future future = completionService.take(); // 这一行没有完成的任务就阻塞

总结一下就是ExecutorCompletionService包裹了ExecutorService之后,我们可以按照执行完成的先后顺序拿到ExecutorCompletionService的要执行的逻辑的返回结果。
代码如下

ExecutorService executor = Executors.newCachedThreadPool();
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executor);

ListenableFuture

从runnable到callable我们实现了没有返回值到有返回值
从futuretask到线程池,我们实现了不用每次都起一个线程的进步
从线程池到CompletionService,我们可以拿到最先完成的任务
现在看future,代码还是真规整的,先提交结果,然后我去做我的事情,等会过来问问是否完成
那么等会过来问问是否完成这个步骤能否省略呢?

有guava的ListenableFuture实现了把"等会过来问问的"步骤省略的工作
来看代码

package com.alibaba.thread;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GuavaTest {

    public static ListeningExecutorService service;
    static {
        int maximumPoolSize = Runtime.getRuntime().availableProcessors() * 4 + 1;
        int corePoolSize = maximumPoolSize / 2;
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 3, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1024), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        executor.allowCoreThreadTimeOut(true);
        service = MoreExecutors.listeningDecorator(executor);
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        // 任务1
        ListenableFuture<Boolean> booleanTask = service.submit(() -> {
            Thread.sleep(10000);
            return true;
        });
        // 老版本的addCallback只有两个参数
        // 新版本的addCallback有三个参数 多的就是最后那个Executor
        Futures.addCallback(booleanTask, new FutureCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean result) {
                log.info("BooleanTask.任务1-10s: " + result);
            }

            @Override
            public void onFailure(Throwable throwable) {
                log.info("BooleanTask.throwable: " + throwable);
            }
        }, service);

        // 任务2
        ListenableFuture<String> stringTask = service.submit(() -> {
            Thread.sleep(3000);
            return "Hello World";
        });

        Futures.addCallback(stringTask, new FutureCallback<String>() {
            @Override
            public void onSuccess(String result) {
                log.info("StringTask.任务2-3s: " + result);
            }

            @Override
            public void onFailure(Throwable t) {}
        }, service);

        // 任务3
        ListenableFuture<Integer> integerTask = service.submit(() -> {
            Thread.sleep(2000);
            return new Random().nextInt(100);
        });

        Futures.addCallback(integerTask, new FutureCallback<Integer>() {
            @Override
            public void onSuccess(Integer result) {
                log.info("IntegerTask.任务3-2s:: " + result);
            }

            @Override
            public void onFailure(Throwable t) {}
        }, service);

        // 执行时间
        log.info("time: " + (System.currentTimeMillis() - start));
    }

}

CompletableFuture

CompletableFuture这个东西能干啥呢?
我的理解是可以链式指定各个任务之间的顺序,例如完成了任务1和任务2才能开始任务3;任务4和任务5完成了一个之后就去完成任务6。

眼前有景道不得,崔颢题诗在上头!
大家参考
https://juejin.cn/post/6844904195162636295

参考资料

https://www.cnblogs.com/lightdb/p/11829397.html
https://juejin.cn/post/6844904195162636295

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/603985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue+springboot餐厅美食菜品评价系统4d5g9

餐厅是一个传统的行业。随着当今社会的发展&#xff0c;时代的进步&#xff0c;行业也在发生着变化&#xff0c;单就点菜这一方面&#xff0c;菜品评价正在逐步进入人们的生活。传统的菜品评价&#xff0c;不仅会耗费大量的人力、时间&#xff0c;有时候还会出错。网上可以解决…

ORTP库局域网图传和VLC实时预览

​ 1.ORTP的引入 1.1、视频网络传输的2种方式 (1)基于下载&#xff1a;http or ftp&#xff08;网站播放视频&#xff0c;追求清晰度&#xff0c;哪怕时间晚一点&#xff09; (2)基于实时&#xff1a;RTP/RTSP/RTCP&#xff08;直播、监控&#xff0c;追求实时&#xff0c;…

Linux 实操篇-组管理和权限管理

Linux 实操篇-组管理和权限管理 Linux 组基本介绍 在linux 中的每个用户必须属于一个组&#xff0c;不能独立于组外。在linux 中每个文件有所有者、所在组、其它组的概念。 所有者所在组其它组改变用户所在的组 文件/目录所有者 一般为文件的创建者,谁创建了该文件&#x…

First Order Motion Model for Image Animation 笔记

First Order Motion Model for Image Animation 摘要 Image animation consists of generating a video sequence so that an object in a source image is animated according to the motion of a driving video. Our framework addresses this problem without using any a…

表情识别(从原理到代码安装)

1. 项目介绍 面青识别(face_classification )是一个基于深度学习的面部表情识别项目,它使用 Keras 和 TensorFlow 框架来实现模型的训练和预测。该项目的主要目标是在图像或视频中检测并识别人脸表情,并将其分类为七种不同的情绪类别:生气、厌恶、害怕、高兴、平静、伤心…

JVM学习笔记(完结)

类加载与字节码技术 1、类文件结构 通过 javac 类名.java 编译 java 文件后&#xff0c;会生成一个 .class 的文件&#xff01; 以下是字节码文件&#xff1a; 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00…

全面大涨原因!多家基金解读

周五&#xff01;大涨&#xff01; 6月2日&#xff0c;A股以强势反弹结束本周的交易&#xff0c;整体全面上行&#xff0c;几乎所有主流指数都收涨。沪指高开高走&#xff0c;深成指、创业板指涨超1%。总体来看&#xff0c;个股涨多跌少&#xff0c;两市超3300股处于上涨状态。…

Linux4.4网页与安全优化

文章目录 计算机系统5G云计算第一章 LINUX Apache网页与安全优化一、网页压缩1.检查是否安装 mod_deflate 模块2.如果没有安装mod_deflate 模块&#xff0c;重新编译安装 Apache 添加 mod_deflate 模块3.配置 mod_deflate 模块启用4.检查安装情况&#xff0c;启动服务5.测试 mo…

redis第三章-redis集群redisCluster

1.redis集群模式比较 &#xff08;1&#xff09;哨兵模式 哨兵模式是利用哨兵来做主从切换的&#xff0c;当主节点发生故障的时候&#xff0c;通过哨兵去选取出一个从节点作为主节点&#xff0c;但本身哨兵的配置还是有些麻烦&#xff0c;并且实际上哨兵的性能和高可用性一般…

chatgpt赋能python:使用Python创建结构体:完全指南

使用Python创建结构体&#xff1a;完全指南 在Python编程领域&#xff0c;结构体是一种非常方便和有用的数据类型&#xff0c;用于存储和组织相关变量。在本篇文章中&#xff0c;我们将讨论如何使用Python创建结构体。让我们开始吧&#xff01; 什么是结构体&#xff1f; 结…

shell学习

1、/etc/hosts的作用 Windows下的目录C:\Windows\System32\drivers\etc\hosts Linux下目录/etc/hosts 如 我们在/etc/hosts文件中添加一行 39.156.66.10 taobao.com 原理是&#xff0c;我们在浏览器输入 taobao.com&#xff0c;那么网站就可以打开百度的网站 但是现实是网…

7大常用ES6特性,助力你写出更现代化的JavaScript

文章目录 1. 模板字符串2. 箭头函数3. let 和 const4. 解构赋值5. 函数默认参数6. 模块化7. Promise 1. 模板字符串 模板字符串是一种新的字符串类型&#xff0c;它允许你在字符串中插入变量&#xff0c;方便了JavaScript开发者的开发体验。 ES6的模板字符串&#xff08;Templa…

demo:搜索帮助出口

写报表&#xff0c;用到搜索帮助&#xff0c;太久不写了&#xff0c;忘了&#xff0c;然后简单测了下。 当然方法很多&#xff0c;我只是突然想起这个东西来了&#xff0c;就测了下&#xff0c;条条大路通北京&#xff0c;想咋实现就咋实现吧&#xff0c;实现了就得了~ 代码很简…

chatgpt赋能python:Python代码教你删除空文件夹——让你的电脑系统更健康

Python代码教你删除空文件夹——让你的电脑系统更健康 作为一名有着10年python编程经验的工程师&#xff0c;我发现在电脑里存储着太多的空文件夹时会对电脑系统造成负面影响。空文件夹占用了硬盘空间&#xff0c;这可能导致你的电脑运行缓慢或者存储空间不足。因此&#xff0…

Qt下使用Sqlite数据库实现图像的读写显示

系列文章目录 提示&#xff1a;这里是该系列文章的所有文章的目录 第一章&#xff1a; Qt连接Sqlite3并使用Qtableview实时显示数据&#xff0c;重写QSqlQueryModel实现文本居中 第二章&#xff1a; Qt下使用Sqlite数据库实现图片的读写显示 文章目录 系列文章目录前言一、初始…

python3.10在centos下安装以及配置

python在centos下安装以及配置 1.背景 centos下默认的都是python2.7下载需要更换为3.x使用&#xff0c;目前大部分应用都是基于pyhton3了 具体步骤&#xff1a; 先按装openssh 不安装会报错 而且要安装高版本 要不然不兼容 报错如&#xff1a; WARNING: pip is configured …

chatgpt赋能python:Python创建程序的SEO指南

Python创建程序的SEO指南 Python是一种流行的高级编程语言&#xff0c;被广泛用于开发Web应用程序、人工智能、数据分析和科学计算等领域。在创建Python程序时&#xff0c;也要考虑SEO因素&#xff0c;以优化网页在搜索引擎结果中的排名。本文将介绍如何创建具有SEO友好性的Py…

软考A计划-电子商务设计师-电子商务系统开发知识

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析

登录用户数据的获取 一、SecurityContextHolder 源码分析ListeningSecurityContextHolderStrategy 使用案例SecurityContextPersistenceFilter 说明 二、登录用户数据的获取三、总结 在【深入浅出Spring Security&#xff08;一&#xff09;】Spring Security的整体架构 中叙述…

Gradle 介绍,根据 Gradle 官方文档整理

这部分内容主要根据 Gradle 官方文档整理&#xff0c;做了对应的删减&#xff0c;主要保留比较重要的部分&#xff0c;不涉及实战&#xff0c;主要是一些重要概念的介绍。 Gradle 这部分内容属于可选内容&#xff0c;可以根据自身需求决定是否学习&#xff0c;目前国内还是使用…