Java并发编程——线程创建的4种常见方式

news2024/9/26 5:21:39

文章目录

    • 一、继承 Thread 类创建创建线程类
      • 1.1 Thread 类解析
      • 1.2 使用方法
      • 1.3 优缺点
    • 二、实现 Runable 接口创建线程类
      • 2.1 Runable 接口解析
      • 2.2 使用方法
      • 2.3 优缺点
    • 三、使用 Callable 和 FutureTask 创建线程
      • 3.1 Callable 接口解析
      • 3.2 RunnableFuture 接口解析
      • 3.3 Future 接口解析
      • 3.4 Futuretask 类解析
      • 3.5 使用方法
      • 3.6 优缺点
    • 四、通过线程池创建线程
      • 4.1 Excutor 接口
      • 4.1 静态工厂类 Executors
      • 4.2 ExecutorService 接口
      • 4.3 使用方法
      • 4.5 优缺点
    • 参考资料

一、继承 Thread 类创建创建线程类

1.1 Thread 类解析

Thread 类里面,成员变量 target 用于保存一个实现了 Runnable 接口的对象,该对象包含了要在新线程中执行的代码。run 方法定义线程里面被执行的用户业务逻辑 。start 方法会启动一个新的线程,并调用 run 方法。Thread 类的部分代码如下:

package java.lang;
public class Thread implements Runnable {
	/* What will be run. */
    private Runnable target;
    
     @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    // 
    public synchronized void start() {
    	...
    }
}

另外,target 目标是 Runnable 示例,我们可以这样任务:线程的本质依旧是通过实现 Runnable 接口来定义任务。线程的运行行为依赖于 Runnable 接口中的 run 方法。

1.2 使用方法

我们可以通过继承 Thread 类并重写其 run 方法来创建线程。 示例如下:

public class MyThread extends Thread {
	@Override
    public void run(){
        System.out.println("MyThread is running");
    }

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

在这个例子中,我们定义了一个 MyThread 类继承自 Thread,并重写了 run 方法。在 main 方法中,我们创建了一个 MyThread 对象,并调用 start 方法启动线程。

1.3 优缺点

优点有:

  • 简单直观

缺点有:

  • 单继承限制
  • 不符合面向对象设计:将任务逻辑和线程管理合并在一个类中不符合面向对象的设计原则,任务逻辑和线程管理应该分开,以提高代码的复用性和可维护性。

二、实现 Runable 接口创建线程类

2.1 Runable 接口解析

Runnable 接口只有一个抽象方法 run(),用于定义被执行的用户业务逻辑。当我们将 Runnable 实例作为参数传递给 Thread 类的构造函数,并将其赋值给 Thread 实例的 target 属性后,Runnable 接口的 run() 方法将会在新的线程中被异步调用。

Runnable 接口的完整代码如下:

package java.lang;
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

2.2 使用方法

我们可以实现 Runable 接口创建线程类, 示例如下:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable is running");
    }

    public static void main(String[] args) {
        // 1. 普通的创建方式
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();  // 启动线程
        
        // 2. 使用匿名内部类实现Runnable接口
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 这里是线程执行的代码
                System.out.println("Hello from anonymous class!");
            }
        }).start();

        // 3. 使用Lambda表达式实现Runnable接口
        new Thread(() -> {
            // 这里是线程执行的代码
            System.out.println("Hello from Lambda!");
        }).start();
    }
}

2.3 优缺点

优点有:

  • 避免单继承的限制:Java 不支持多继承,通过实现 Runnable 接口,可以使类继承其他类

  • 逻辑和数据分离:通过实现 Runnable 接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。

缺点有:

  • 无法直接获取线程状态:由于 Runnable 实现类本身不是线程对象,因此无法直接获取和控制线程的状态。必须通过 Thread.currentThread() 获取当前线程实例,才能访问和控制当前线程。
  • 任务执行结果的处理Runnable 接口中的 run 方法不返回结果,如果需要任务执行的结果,必须额外设计一种机制来获取结果。相比之下,Callable 接口更适合这种场景。

三、使用 Callable 和 FutureTask 创建线程

3.1 Callable 接口解析

Callable 接口是一个支持泛型的函数式接口,源代码如下:

package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable 接口类似于 Runnable。比较而言,Callable 接口的 call() 有返回值,并且声明了受检异常,其功能更强大一些。

注意:Callable 实例和 Runable 实例不一样,不能作为 Thread 线程实例的 target 来使用targetRunable 类型的变量 )

3.2 RunnableFuture 接口解析

RunnableFuture 接口是为了在 CallableThread 之间实现搭桥功能。RunnableFuture 接口实现了两个目标:

  • 可以作为 Thread 线程实例的 target 实例。RunnableFuture 继承了 Runnable 接口,从而保证了其实例可以作为 Thread 线程实例的 target 目标;
  • 可以获取异步执行的结果。RunnableFuture 通过继承 Future 接口,保证了通过它可以获取未来的异步执行结果

源代码如下:

package java.util.concurrent;
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

3.3 Future 接口解析

Future 接口通常用于实现异步编程和回调机制, 至少提供了三大功能:

  • 能够取消异步执行中的任务。
  • 判断异步任务是否执行完成。
  • 获取异步任务完成后的执行结果

源代码如下:

package java.util.concurrent;
public interface Future<V> {
    // 判断计算是否已经完成。
    boolean isDone();

    // 取消异步执行
    boolean cancel(boolean mayInterruptIfRunning);
    
    // 判断异步执行是否被取消。
    boolean isCancelled();
 
    // 获取异步任务完成后的执行结果
    V get() throws InterruptedException, ExecutionException;

    //  获取异步任务完成后的执行结果, 等待时间不超过指定的超时时间。
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

注意:Future 接口提供了 get() 方法来获取任务的结果。这个方法的行为是同步的,具体表现为:

  • 阻塞行为:当你调用 future.get() 时,如果任务尚未完成,当前线程将被阻塞,直到任务完成并返回结果。
  • 获取结果:在任务完成后,get() 方法将返回任务的结果,或者抛出异常(如果任务执行过程中发生了异常)。

虽然任务的执行是异步的,但通过 Futureget() 方法获取结果的过程是同步的,因为调用 get() 方法的线程必须等待任务完成。

3.4 Futuretask 类解析

FutureTask 类是 RunnableFuture 接口的实现类,提供了对异步任务的操作的具体实现,实现了两个目标:

  • 可以作为 Thread 线程实例的 target 实例。
  • 可以获取异步执行的结果。

在这里插入图片描述

FutureTask 的部分代码如下:

public class FutureTask<V> implements RunnableFuture<V> {
	/** The underlying callable; nulled out after running */
    private Callable<V> callable;
    
     /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    
    ...
}

其中,callable 实例属性保存异步执行的任务,outcome 实例属性用于保存 callable成员call() 方法的异步执行结果。

3.5 使用方法

通过 FutureTask 类和 Callable 接口的联合使用可以创建能获取异步执行结果的线程。具体步骤如下:

  1. 创建 Callable 实现类:定义异步执行的逻辑并返回结果。
  2. 创建 FutureTask 实例:将 Callable 实例传入 FutureTask
  3. 创建并启动 Thread 实例:将 FutureTask 实例作为 Thread 的 target 属性,启动新线程。
  4. 获取结果:通过 FutureTaskget() 方法阻塞性地获取执行结果。

异步执行:指任务可以并行或在后台执行,主线程不必等待任务完成,可以继续处理其他操作。当异步任务完成时,可以通过回调、通知或其他机制获取结果。

示例如下:

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 异步执行的具体逻辑
        return "Task executed";
    }

    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);

        // 使用 `FutureTask` 实例作为 `Thread` 构造器的 target 入参,构造新的 `Thread` 线程实例
        Thread thread = new Thread(futureTask, "returnableThread");

        // 启动新线程
        thread.start();

        try {
            // 阻塞性地获得并发线程的执行结果
            String result = futureTask.get();
            System.out.println("Result from callable: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这个例子中有两个线程:

  • main 线程:执行 main() 方法的主线程。
  • returnableThread 线程:由 main 线程通过 thread.start() 方法启动的业务线程。该线程包含了 FutureTask 任务作为其 target

当 main 线程通过 thread.start() 启动 returnableThread 线程后,main 线程会继续执行自己的任务,而 returnableThread 线程开始并发执行。其中,returnableThread 线程的执行逻辑为:

returnableThread 线程首先执行 thread.run() 方法。在 thread.run() 方法中,会调用其 target(即 FutureTask 实例)的 run() 方法。

②在 FutureTask.run() 方法中,会调用内部 callable 成员的 call() 方法。这个 callable 成员是在 FutureTask 构造器初始化时传递进来的,是一个自定义的 Callable 实现类实例(如 ReturnableTask)。

FutureTask 内部的 callable 成员(ReturnableTask 实例)执行其 call() 方法。结果保存在FutureTask 内部的 outcome 实例属性。

在这里插入图片描述

3.6 优缺点

优点

  • 实现异步编程和回调机制

  • 支持任务结果的返回和异常处理。

  • 可以与线程池结合使用,任务管理更加灵活。

  • 提供了对任务执行状态的控制和结果的同步等待。

缺点

  • 引入了额外的复杂性和性能开销。
  • 任务取消可能会受到实际实现的影响。

四、通过线程池创建线程

由于线程实例的创建于销毁的代价过高,我们要使用线程池的技术。线程池是多线程编程中的一种重要机制,可以有效地管理和复用线程资源,从而提高应用程序的性能和响应能力。

4.1 Excutor 接口

Executor 是 Java 并发包 (java.util.concurrent) 中的一个核心接口,提供了一个基本的任务执行框架

Executor 接口定义了一个执行任务的方法,使任务的提交与任务的执行机制解耦。这种解耦允许开发者专注于任务的逻辑,而不需要关心任务是如何、何时以及在哪个线程中执行的。

package java.util.concurrent;
public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

4.1 静态工厂类 Executors

Executors 是一个工具类,包含了静态工厂方法,用于创建 ExecutorService 实例。它简化了线程池的创建过程,使得创建线程池变得更加方便和直观。

线程池类型方法说明
固定大小线程池Executors.newFixedThreadPool(int nThreads)创建一个固定大小的线程池,线程池中的线程数量为 nThreads
缓存线程池Executors.newCachedThreadPool()创建一个缓存线程池,根据需要创建新线程,但会复用空闲线程
单线程池Executors.newSingleThreadExecutor()创建一个单线程池,所有任务按顺序执行
调度线程池Executors.newScheduledThreadPool(int corePoolSize)创建一个调度线程池,可以执行定时或周期性任务

4.2 ExecutorService 接口

ExecutorService 是一个接口,继承自 Executor,它定义了管理和控制任务执行的方法ExecutorService 实现类可以通过 Executors 工具类来创建。

ExecutorService 提供了一些扩展功能,比如任务提交、任务调度、关闭线程池等。主要功能有:

  • 任务提交: 可以提交 RunnableCallable 任务。
  • 任务调度: 提供方法来调度任务的执行时间和频率(如果是 ScheduledExecutorService)。
  • 线程池管理: 提供方法来管理线程池的生命周期,包括关闭线程池、等待线程池完成任务等。

ExecutorService 线程池提交异步执行 target 目标任务的常用方法有:

任务类型方法说明
Runnable 任务execute(Runnable command)提交一个不返回结果的任务
Callable 任务submit(Callable<T> task)提交一个有返回结果的任务,返回一个 Future 对象
定时 Runnable 任务schedule(Runnable command, long delay, TimeUnit unit)提交一个在指定延迟后执行的任务
定时 Callable 任务schedule(Callable<V> callable, long delay, TimeUnit unit)提交一个在指定延迟后执行的任务,并返回一个 Future 对象
周期性 Runnable 任务scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)提交一个在初始延迟后开始执行,并以固定频率重复执行的任务
周期性 Runnable 任务scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)提交一个在初始延迟后开始执行,并在每次执行完后等待指定时间再执行

4.3 使用方法

public class ThreadPoolExample {

    public static void main(String[] args) {
        // 创建一个包含3个线程的固定大小线程池 ThreadPoolExecutor
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 提交10个任务给线程池
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Task ID: " + taskId + " is running by " + Thread.currentThread().getName());
                    try {
                        // 假设每个任务需要1秒的时间来完成
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        System.out.println("Task interrupted");
                    }
                }
            });
        }

        // 关闭线程池,等待所有任务完成
        executor.shutdown();
        try {
            // 等待所有任务结束,或超时后退出
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            System.err.println("Tasks interrupted");
        }

        System.out.println("All tasks completed.");
    }
}

在这个例子中,我们创建了一个包含三个线程的线程池。然后,我们提交了十个任务给这个线程池,每个任务都会打印它的 ID 以及执行它的线程名称,并模拟了一个 1 秒的延迟。最后,我们调用了 shutdown 方法来关闭线程池,并调用 awaitTermination 方法等待所有任务完成。
请注意,shutdown 方法并不会立即停止正在运行的任务,而是不允许提交新的任务。awaitTermination 方法则会阻塞主线程直到所有已提交的任务完成或达到指定的超时时间。

4.5 优缺点

优点:

  • 提供了线程管理和任务调度的机制,避免了频繁创建和销毁线程的开销。

缺点:

  • 资源耗尽风险:如果线程池的配置不当,可能会导致资源耗尽。例如,如果线程池的核心线程数设置过低,可能会导致任务队列中的任务排队等待,影响响应时间。
  • 复杂性增加:线程池的使用增加了程序的复杂性。需要合理配置线程池的参数(如核心线程数、最大线程数、任务队列长度等),并处理任务的提交、监控和管理。

参考资料

Overview (Java SE 17 & JDK 17) (oracle.com)

《 极致经典(卷2):Java高并发核心编程(卷2 加强版) -特供v21-release》

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

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

相关文章

CV/NLP【面经】

CV/NLP【面经】 1x1conv的作用多头注意力与普通注意力的区别&#xff1a;过拟合怎么解决随机森林和GBDT的区别决策树是怎么做回归任务的注意力机制参数量和计算量 1x1conv的作用 1.首先我们需要明确卷积的输入输出不是一个平面&#xff0c;而是一个长方体。所以1x1conv实际上对…

CTFHUB-web-RCE-命令注入

开启题目 检查网页显示内容&#xff0c;可以直接看到源代码。大致意思是&#xff1a;检查用户输入的 GET 请求&#xff0c;判断用户是否输入了 ip 信息。如果输入了 ip 信息&#xff0c;则使用用户输入的这个 ip 数据执行一个 shell 命令 "ping -c 4" 。 在输入框中…

C语言----用指针数组构造字符串数组

用指针数组构造字符串数组 实例说明&#xff1a; 本实例实现输入一个星期中对应的第几天&#xff0c;可显示其英文写法。例如&#xff0c;输入“4”&#xff0c;则显示星期四所对应的英文名。 实现过程&#xff1a; (1)打开 Visual C 6.0开发环境&#xff0c;新建一个C源文件…

【Matlab】零阶保持法:连续状态空间方程(含噪声,非线性)离散化处理方法

常用的离散化方法&#xff1a;零阶保持器&#xff08;zero-order hold&#xff0c;ZOH&#xff09;、欧拉法&#xff08;Euler&#xff09;、一阶保持器&#xff08;First-order hold&#xff0c;FOH&#xff09;。最常用的是前两种。 0.零阶保持器&#xff08;ZOH&#xff09…

软文写作必须掌握的技巧有哪些?

现代互联网飞速发展的时代&#xff0c;硬广逐渐变的效果越来越差&#xff0c;而软文推广已经成为网络营销的重要组成部分了&#xff0c;一篇好的软文往往能为你的产品、网站带来意想不到的效果。 用于做营销的软文&#xff0c;我们不能像写普通文章那样随意。一篇优质的软文会让…

AI面试:未来招聘的新常态?

一、引言&#xff1a;招聘变革的序章 在数字化浪潮的推动下&#xff0c;各行各业正经历着前所未有的变革&#xff0c;而人力资源管理作为企业发展的核心驱动力之一&#xff0c;也迎来了其转型升级的关键时刻。传统招聘流程中&#xff0c;简历筛选、初步面试、复试乃至终面&…

AI的IDE:Cursor配置虚拟python环境(conda)

AI的IDE&#xff1a;Cursor配置虚拟python环境&#xff08;conda&#xff09; Cursor是一个AI的IDE&#xff0c;是从VSCode源代码中fork出来的&#xff0c;专注于和AI一起Coding而生。https://www.cursor.com/是官方地址。最近开始逐渐的试用Cursor&#xff0c;之前一直是VSCod…

vue3数字动画插件countup.js

countup.js 是一个轻量级的 JavaScript 库&#xff0c;允许你为网站上显示统计数据或计数器时平滑地动画化数字。CountUp 类被用来创建计数器实例&#xff0c;可以平滑地从一个数值变化到另一个数值。 只是用户视觉更好一些。 1、安装插件 npm i countup.js2、个人是封装了组件…

前端性能优化-用户交互体验优化

前言 本文总结一些常见的用户交互体验优化的方法 骨架屏 使用 loading 图或者是骨架屏&#xff0c;可以一定程度上减少用户等待加载的焦虑感&#xff0c;让用户感觉没有等太久&#xff0c;这就是用户体验级的优化。 PS&#xff1a; 可以利用 webpack 实现骨架屏的自动生成 图…

Grafana动态视图在转转推送系统中的应用

1 背景 转转的推送系统为自研产物&#xff0c;实现过程简述为&#xff1a;对外提供一个接入层&#xff0c;以多次MQ转发形式进行多系统交互&#xff0c;内部包括&#xff1a;推送内容过滤、免打扰策略、厂商通道分发等逻辑&#xff0c;最终发起http请求至厂商通道&#xff0c;…

【Android Studio】新new UI老UI的切换

Android studio新版UI 风格和老版本风格切换&#xff1a; File-> Settings-> Appearance-> New UI-> Enable NewUI选项

人工智能大模型如何引领智能时代的革命?

人工智能大模型如何引领智能时代的革命&#xff1f; 人机交互革命&#xff1a;大模型如何提升我们与机器沟通的自然性和智能化程度&#xff1f;计算范式革命&#xff1a;大模型如何影响现有的计算模式&#xff0c;并推动新一代计算技术的演进&#xff1f;认知协作革命&#xff…

海南云亿商务咨询有限公司洞察抖音电商新趋势

在当今这个数字化浪潮汹涌的时代&#xff0c;抖音电商以其独特的魅力&#xff0c;正逐步成为企业转型升级、品牌破圈的重要阵地。而在这片充满机遇与挑战的蓝海中&#xff0c;海南云亿商务咨询有限公司犹如一颗璀璨的新星&#xff0c;凭借其专业的服务能力和敏锐的市场洞察&…

SQL手工注入

目录 1.判断是否存在sql注入点 1.1我们在地址栏中输入?id1 1.2我们在地址栏中输入?id-- 2.联合查询 2.1首先知道表格有几列&#xff0c;如果报错就是超过列数&#xff0c;如果显示正常就是没有超出列数。 2.2爆出显示位&#xff0c;就是看看表格里面哪一列是在页面显示…

[ACM MM 2024] AesExpert 面向图像审美知觉的多模态基础模型

AesExpert: Towards Multi-modality Foundation Model for Image Aesthetics Perception GitHub (arxiv.org) 动机 1. 要解决的问题 图像美学感知的高度抽象性&#xff1a;图像美学感知&#xff08;IAP&#xff09;的高度抽象性对当前的多模态大语言模型&#xff08;MLLMs&…

Redis 7.x 系列【38】缓存预热、缓存雪崩、缓存穿透、缓存击穿

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 缓存预热2. 缓存雪崩3. 缓存穿透4. 缓存击穿 1. 缓存预热 关键词&#xff1a;预先加…

某MDM主数据管理系统与蓝凌OA系统集成案例

一、项目背景 某客户使用OA在集团中处于一个重要角色&#xff0c;集团内各流程业务数据都需要通过OA进行审批下发&#xff0c;同某MDM主数据之间进行数据的交互&#xff0c;员工、供应商、法人组织、会计科目等主数据流程&#xff0c;实现各业务板块系统间的业务联通&#x…

动手学深度学习V2每日笔记(使用块的网络VGG)

本文主要参考沐神的视频教程 https://www.bilibili.com/video/BV1Ao4y117Pd/spm_id_fromautoNext&vd_sourcec7bfc6ce0ea0cbe43aa288ba2713e56d 文档教程 https://zh-v2.d2l.ai/ 本文的主要内容对沐神提供的代码中个人不太理解的内容进行笔记记录&#xff0c;内容不会特别严…

postgregSQL配置vector插件

1.下载vector 下载vector&#xff1a;https://pgxn.org/dist/vector/0.5.1/ 放在&#xff1a;C:\Program Files\PostgreSQL\vector-0.5.1 2.安装Visual Studio 2022 下载&#xff1a;https://visualstudio.microsoft.com/zh-hans/downloads/ 安装Visual Studio是为了C编译环…

JL-杰理芯片-认识TA的SDK的第六天

通过修改代码无法解决的错误解决问题的方法: 从头开始一点点的配置,并运行。(配置的是标准SDK) 将无法修改的错误打印到xshell中,去看看是什么原因,就算不能理解,也要看看他运行了几次。 上电开机和按键开机1T1,2T1,一拖二