探索 Java 线程的创建

这篇具有很好参考价值的文章主要介绍了探索 Java 线程的创建。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

by emanjusaka from ​ https://www.emanjusaka.top/2023/09/java-thread-create 彼岸花开可奈何
本文欢迎分享与聚合,全文转载请留下原文地址。

前言

在并发编程中我们为啥一般选用创建多个线程去处理任务而不是创建多个进程呢?这是因为线程之间切换的开销小,适用于一些要求同时进行并且又要共享某些变量的并发操作。而进程则具有独立的虚拟地址空间,每个进程都有自己独立的代码和数据空间,程序之间的切换会有较大的开销。下面介绍几种创建线程的方法,在这之前我们还是要先了解一下什么是进程什么是线程。

一、什么是进程和线程

线程是进程中的一个实体,它本身是不会独立存在的。进程是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

进程和线程的关系图如下:

从上面的图中,我们可以知道一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器和栈区域。堆是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的,堆里面主要存放使用new操作创建的对象实例。方法区则用来存放 JVM 加载的类、常量及静态变量等信息,也是线程共享的。

二、线程的创建

Java 中有几种线程创建的方式:

  • 实现 Runnable 接口的 run 方法

  • 继承 Thread 类并重写 run 的方法

  • 使用 FutureTask 方式

  • 使用线程池创建

2.1、实现 Runnable 接口的 run 方法

public static void main(String[] args) {
        RunableTask task = new RunableTask();
        new Thread(task).start();
        new Thread(task).start();
    }

    public static class RunableTask implements Runnable {
        @Override
        public void run() {
            System.out.println("I am a child thread");
        }
    }
// 输出
I am a child thread
I am a child thread

这段代码创建了一个RunableTask类,该类实现了Runnable接口,并重写了run()方法。在run()方法中,它打印了一条消息:"I am a child thread"。

接下来是main()方法,它是Java程序的入口点。在main()方法中,首先创建了一个RunableTask对象,然后通过调用Thread类的构造函数将该对象作为参数传递给Thread类的构造函数,创建了两个新的线程对象。这两个线程对象分别使用start()方法启动,从而使得每个线程都能够并发地执行。

当程序运行时,会创建两个子线程,它们将并发地执行RunableTask对象的run()方法。由于两个线程是同时运行的,因此它们可能会交替执行run()方法中的代码。在这种情况下,由于线程调度的不确定性,可能会出现以下情况之一:

  • 第一个线程先执行run()方法,打印出"I am a child thread"。
  • 第二个线程先执行run()方法,打印出"I am a child thread"。

需要注意的是,由于线程的执行顺序是不确定的,所以每次运行程序时,输出的结果可能会有所不同。

2.2、继承 Thread 类方式的实现

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

    //继承Thread类并重写run方法
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("I am a child thread");
        }
    }

创建一个名为MyThread的类,该类继承了Thread类,并重写了run()方法。

2.3、用 FutureTask 的方式

 public static void main(String[] args) throws InterruptedException {
        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        //启动线程
        new Thread(futureTask).start();
        try {
            //等待任务执行完毕,并返回结果
            String result = futureTask.get();
            System.out.println(result);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    //创建任务类,类似Runable
    public static class CallerTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "hello emanjusaka";
        }
    }

上面使用了FutureTaskCallable接口来实现异步任务的执行。首先,在main()方法中创建了一个FutureTask对象,并将一个匿名内部类CallerTask的实例作为参数传递给它。这个匿名内部类实现了Callable接口,并重写了call()方法。在call()方法中,它返回了一个字符串"hello emanjusaka"。接下来,通过调用FutureTask对象的start()方法启动了一个新的线程,该线程会执行CallerTask对象的call()方法。由于start()方法是异步执行的,主线程会继续执行后续的代码。然后,使用futureTask.get()方法来等待异步任务的执行结果。这个方法会阻塞当前线程,直到异步任务执行完毕并返回结果。如果任务执行过程中发生了异常,可以通过捕获ExecutionException来处理异常情况。

需要注意的是,由于异步任务的执行是并发进行的,因此输出的结果可能会有所不同。另外,由于FutureTaskCallable接口提供了更灵活和强大的功能,因此在需要处理返回结果或处理异常的情况下,它们比继承Thread类并重写run()方法的方式更加方便和可靠。

2.4、使用线程池

  • Executors

    package top.emanjusaka;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Main {
        public static void main(String[] args) {
            // 创建一个固定大小的线程池,大小为5
            ExecutorService executor = Executors.newFixedThreadPool(5);
    
            // 提交10个任务到线程池中执行
            for (int i = 0; i < 10; i++) {
                Runnable worker = new WorkerThread("" + i);
                executor.execute(worker);
            }
    
            // 关闭线程池
            executor.shutdown();
            while (!executor.isTerminated()) {
            }
    
            System.out.println("所有任务已完成");
        }
    }
    
    class WorkerThread implements Runnable {
        private String command;
    
        public WorkerThread(String s) {
            this.command = s;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 开始处理任务: " + command);
            processCommand();
            System.out.println(Thread.currentThread().getName() + " 完成任务: " + command);
        }
    
        private void processCommand() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // 输出
    pool-1-thread-1 开始处理任务: 0
    pool-1-thread-2 开始处理任务: 1
    pool-1-thread-3 开始处理任务: 2
    pool-1-thread-4 开始处理任务: 3
    pool-1-thread-5 开始处理任务: 4
    pool-1-thread-2 完成任务: 1
    pool-1-thread-4 完成任务: 3
    pool-1-thread-2 开始处理任务: 5
    pool-1-thread-4 开始处理任务: 6
    pool-1-thread-1 完成任务: 0
    pool-1-thread-3 完成任务: 2
    pool-1-thread-5 完成任务: 4
    pool-1-thread-3 开始处理任务: 8
    pool-1-thread-1 开始处理任务: 7
    pool-1-thread-5 开始处理任务: 9
    pool-1-thread-2 完成任务: 5
    pool-1-thread-4 完成任务: 6
    pool-1-thread-1 完成任务: 7
    pool-1-thread-3 完成任务: 8
    pool-1-thread-5 完成任务: 9
    所有任务已完成
    

上面的例子中我们首先创建了一个大小为5的线程池。然后,我们提交了10个任务到线程池中执行。每个任务都是一个实现了Runnable接口的WorkerThread对象。最后,我们关闭线程池并等待所有任务完成。

阿里巴巴开发规范建议使用ThreadPoolExecutor来创建线程池,而不是直接使用Executors。这样做的原因是,Executors创建的线程池可能会存在资源耗尽的风险,而ThreadPoolExecutor则可以更好地控制线程池的运行规则,规避资源耗尽的风险 。

  • ThreadPoolExecutor

    package top.emanjusaka;
    
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class Main {
        public static void main(String[] args) {
            // 创建一个固定大小的线程池,大小为5
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    
            for (int i = 0; i < 10; i++) {
                Runnable worker = new WorkerThread("" + i);
                executor.execute(worker);
            }
    
            // 关闭线程池
            executor.shutdown();
            try {
                executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("所有任务已完成");
        }
    }
    
    class WorkerThread implements Runnable {
        private String command;
    
        public WorkerThread(String s) {
            this.command = s;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 开始处理任务:" + command);
            processCommand();
            System.out.println(Thread.currentThread().getName() + " 完成任务:" + command);
        }
    
        private void processCommand() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    // 输出
    pool-1-thread-1 开始处理任务:0
    pool-1-thread-3 开始处理任务:2
    pool-1-thread-2 开始处理任务:1
    pool-1-thread-4 开始处理任务:3
    pool-1-thread-5 开始处理任务:4
    pool-1-thread-2 完成任务:1
    pool-1-thread-3 完成任务:2
    pool-1-thread-5 完成任务:4
    pool-1-thread-3 开始处理任务:5
    pool-1-thread-4 完成任务:3
    pool-1-thread-1 完成任务:0
    pool-1-thread-4 开始处理任务:8
    pool-1-thread-2 开始处理任务:7
    pool-1-thread-5 开始处理任务:6
    pool-1-thread-1 开始处理任务:9
    pool-1-thread-4 完成任务:8
    pool-1-thread-3 完成任务:5
    pool-1-thread-2 完成任务:7
    pool-1-thread-1 完成任务:9
    pool-1-thread-5 完成任务:6
    所有任务已完成
    

在这个例子中,我们首先创建了一个大小为5的线程池,其中核心线程数为5,最大线程数为10,空闲线程存活时间为200毫秒,工作队列为LinkedBlockingQueue。然后,我们提交了10个任务到线程池中执行。最后,我们关闭线程池并等待所有任务完成。

ThreadPoolExecutor的构造函数有以下参数:

  1. corePoolSize:核心线程数,即线程池中始终保持活跃的线程数。
  2. maximumPoolSize:最大线程数,即线程池中允许的最大线程数。当工作队列满了之后,线程池会创建新的线程来处理任务,直到达到最大线程数。
  3. keepAliveTime:空闲线程存活时间,即当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间。超过这个时间后,空闲线程将被销毁。
  4. unit:keepAliveTime的时间单位,例如TimeUnit.SECONDS表示秒,TimeUnit.MILLISECONDS表示毫秒。
  5. workQueue:工作队列,用于存放待处理的任务。常用的有ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue等。
  6. threadFactory:线程工厂,用于创建新的线程。可以自定义线程的名称、优先级等属性。
  7. handler:拒绝策略,当工作队列满了且线程池已满时,线程池如何处理新提交的任务。常用的有AbortPolicy(抛出异常)、DiscardPolicy(丢弃任务)和DiscardOldestPolicy(丢弃队列中最旧的任务)。
  8. executorListeners:监听器,用于监听线程池的状态变化。常用的有ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.CallerRunsPolicy和ThreadPoolExecutor.DiscardPolicy。

三、总结

使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以。

使用Callable和Future创建线程。这种方式可以将线程作为任务提交给线程池执行,而且可以获取到线程的执行结果。但是需要注意的是,如果线程抛出了异常,那么在主线程中是无法获取到的。使用线程池。线程池是一种管理线程的机制,可以有效地控制线程的数量和复用线程,避免了频繁地创建和销毁线程带来的性能开销。

参考资料

  1. 《Java并发编程之美》

本文原创,才疏学浅,如有纰漏,欢迎指正。尊贵的朋友,如果本文对您有所帮助,欢迎点赞,并期待您的反馈,以便于不断优化。

原文地址: https://www.emanjusaka.top/2023/09/java-thread-create

微信公众号:emanjusaka的编程栈文章来源地址https://www.toymoban.com/news/detail-705975.html

到了这里,关于探索 Java 线程的创建的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java 基础篇】Java多线程编程详解:线程创建、同步、线程池与性能优化

    Java是一门强大的编程语言,其中最引人注目的特性之一是多线程支持。多线程允许我们在同一程序中同时执行多个任务,这大大提高了应用程序的性能和响应能力。本文将深入介绍Java线程的基础知识,无论您是初学者还是有一些经验的开发人员,都将从中获益。 在计算机科

    2024年02月07日
    浏览(57)
  • 谷歌浏览器地址栏不显示http或者https://www.前缀解决办法

    可能与浏览器版本有关,可参考此版本 恢复完整版域名步骤: 1.打开 chrome://flags/ 2.找到 Omnibox on-focus suggestions for the contextual Web 改成 enabled 3…根据提示 relaunch 浏览器 4.右键地址栏 5.完成,刷新浏览器 可参考 http://www.taodudu.cc/news/show-4921051.html?action=onClick

    2024年02月08日
    浏览(49)
  • Java 进阶(7) 创建线程

    1. 定义Thread类的⼦类,并重写该类的run()⽅法,该run()⽅法的⽅法体就代表了线程需要完成的任务,因此把run()⽅法称为线程执⾏体。 2. 创建Thread⼦类的实例,即创建了线程对象 3. 调⽤线程对象的start()⽅法来启动该线程 示例: 测试: 1. 定义Runnable接⼝的实现类,并重写该接⼝

    2023年04月16日
    浏览(46)
  • Java 创建线程的方法

    🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,计算机系列(火速更新中) 💭 格言:种一棵树最好的时间是十年前,其次是现在 🏡动动小手,点个关注不迷路,感

    2024年02月07日
    浏览(41)
  • 探索Java并发编程利器:LockSupport,一种高效的线程阻塞与唤醒机制

    关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 我们继续总结学习 Java基础知识 ,温故知新。 LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用

    2024年02月16日
    浏览(53)
  • Java反射:探索对象创建与类信息获取

    🎉欢迎来到Java学习路线专栏~Java反射:探索对象创建与类信息获取 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:Java学习路线 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和水平有限

    2024年02月09日
    浏览(41)
  • Java多线程(1)---多线程认识、四种创建方式以及线程状态

    目录 前言 一.Java的多线程 1.1多线程的认识  1.2Java多线程的创建方式 1.3Java多线程的生命周期 1.4Java多线程的执行机制 二.创建多线程的四种方式 2.1继承Thread类 ⭐创建线程  ⭐Thread的构造方法和常见属性  2.2.实现Runnable接口 ⭐创建线程 ⭐使用lambda表达式创建 2.3实现Callable接口

    2024年02月14日
    浏览(43)
  • Java多线程---线程的创建(Thread类的基本使用)

    本文主要介绍Java多线程的相关知识, Thread的创建, 常用方法的介绍和使用, 线程状态等. 文章目录 前言 一. 线程和Thread类 1. 线程和Thread类 1.1 Thread类的构造方法 1.2 启用线程的相关方法 2. 创建第一个Java多线程程序 3. 使用Runnable对象创建线程 4. 使用内部类创建线程 5. 使用Lamba

    2024年02月03日
    浏览(40)
  • Java中如何实现多线程,创建并开启新的线程

    提示:以下是本篇文章正文内容,Java系列学习将会持续更新 1.Java线程在代码中是如何体现的   java.lang.Thread类(包括其子类)的一个对象 Thread——线程 2.如何在代码中创建线程(最基本) Runnable——让这个线程去完成的工作 (任务)   ①先创建任务类,并实现Runhable接口。   

    2023年04月12日
    浏览(37)
  • Java 中的线程是什么,如何创建和管理线程-上(十一)

    Java 中的线程是指程序中可以独立执行的最小单位。在 Java 中,创建线程通常有两种方式:继承 Thread 类和实现 Runnable 接口。线程的管理包括控制线程状态、线程优先级、线程同步等。 一、Java 中的线程 线程是程序中能够独立执行的最小单位,它具有自己的执行路径、调用栈

    2024年02月03日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包