深度解析多线程的创建方式和正确启动多线程

news2024/11/25 21:42:23

一、创建多线程

1. 实现多线程

java 实现多线程的方式准确来说有两种(oracle官方文档说的):

(1)实现 Runnable 接口, 重写run()函数,运行start()方法

代码演示:

/**
 * 用Runnable方式创建线程
 */
public class RunnableStyle implements Runnable {
    @Override
    public void run() {
        System.out.println("我是一个Runnable");
    }

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

(2)继承 Thread 类,重写run()函数,运行start()方法

/**
 * 用Thread方式创建线程
 * 每个线程只能操作当前线程类的对象变量,耦合性太强
 */
public class ThreadStyle extends Thread {

    @Override
    public void run() {
        System.out.println("我是一个 Thread");
    }

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

2. 两种实现多线程方法的对比

方法1(实现Runnable接口)更好的三个优势:

(1)解耦性好,run方法业务与线程建立逻辑分离

  • 表现1,这样创建不同的线程可以共享Runnable实例中的变量和方法,即多个线程可以操作同一资源,而Thread只能操作当前对象的业务,即每新建一个线程类,当前资源类变量都会初始化

  • 代码实现区别

    • 实现Runnable接口
    /**
     * 用Runnable方式创建线程
     * 解耦,不同线程可以操作同一资源对象的变量,run业务与线程建立分离
     */
    public class RunnableStyle implements Runnable {
    
        private int count = 10;
    
        @Override
        public void run() {
            count--;
            System.out.println(Thread.currentThread().getName() + " : count=" + count);
        }
    
        public static void main(String[] args) throws InterruptedException {
            RunnableStyle runnableStyle = new RunnableStyle();
            new Thread(runnableStyle,"Runnable1").start();
            Thread.sleep(1000);
            new Thread(runnableStyle, "Runnable2").start();
        }
    }
    
    Runnable1 : count=9
    Runnable2 : count=8
    
    • 继承Thread类
    /**
     * 用Thread方式创建线程
     * 每个线程只能操共享当前线程类的对象变量,耦合性太强
     */
    public class ThreadStyle extends Thread {
    
        private int count = 10;
    
        @Override
        public void run() {
            count--;
            System.out.println(Thread.currentThread().getName() + " : count=" + count);
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadStyle thread1 = new ThreadStyle();
            thread1.setName("Thread1");
            thread1.start();
            Thread.sleep(1000);
            ThreadStyle thread2 = new ThreadStyle();
            thread2.setName("Thread2");
            thread2.start();
        }
    }
    
    Thread1 : count=9
    Thread2 : count=9
    

#### (2)节约资源,每次新建任务无需每次都新建线程,例如线程池类

使用继承Thread的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable的方式,只需要更换Runnable实例,不需要新建Thread类,就可以实现线程的重复利用,线程池就是基于这个原理,从而大大减小损耗。

#### (3)可扩展性好,可用实现多继承接口,不能实现多继承类

继承Thread类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。

### 3. 两种方法的本质对比

方法一和方法二,也就是“实现Runnable接口并传入Thread类”和“继承Thread类然后重写run()”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:

```java
...
public class Thread implements Runnable {
    private Runnable target;
...
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
...
}

方法一:最终调用Runnable实例的run()方法(target.run());

方法二:直接将Thread类的里整个 run() 方法重写

4. 创建线程时同时使用Runnable、Thread两种方法会发生什么?

如果 同时使用Runnable、Thread两种方法,那么只会执行 Thread 中重写的 run()方法,因为将父类中的run方法覆盖了:

...
public class Thread implements Runnable {
    private Runnable target;
...
    // 该 run 方法直接被重写了,便不再会执行 target.run()方法了
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
...
}

同时执行两种方式的测试:

/**
 * 同时执行两种方式的测试
 * 匿名内部类
 */
public class DoubleStyle {
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("我是 Runnable");
        }) {
            @Override
            public void run() {
                System.out.println("我是 Thread");
            }
        }.start();
    }
}

执行结果:

我是 Thread 的 run方法

准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:一是实现Runnable接口的run方法,并把Runnable实例传给Thread类,二是重写Thread的run方法(继承Thread类)

5. 其它创建线程的方式

其它创建线程的方式也有很多,但都是在代码的写法上的千变万化,本质上还是都基于 Thread 类和 Runnable 类的重写run()方法。比如以下几种方式:

(1)线程池创建线程

  • 线程池本质上,是创建有限个线程排队处理多于线程数量的业务,为了节省创建和销毁线程的时间
  • 本质上还是实现Runnable接口创建线程类
  • 源码片段:

(2)通过Callable和FutureTask创建线程

  • Callable和FutureTask类同时继承了Future接口和Runnnable接口,本质还是实现Runnnable接口中的run方法

(3)定时器也可以创建线程

演示代码:

/**
 * 描述:     定时器创建线程
 */
public class DemoTimmerTask {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }, 1000, 1000);
    }
}

其中 TimerTask 又是实现了 Runnable 接口 的类。

所以,本质上,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:一是实现Runnable接口的run方法,并把Runnable实例传给Thread类,二是重写Thread的run方法(继承Thread类)

二、启动多线程

1. start()和run()对比

代码测试:

public class StartAndRunMethod {

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());
        };
        runnable.run();

        new Thread(runnable).start();
    }
}

测试结果:

main
Thread-0

可见 run() 只是在主线程中调用了 run 方法,没有启动子线程,这显然不符合我们开启新线程的预期;而 start() 启动了一个子线程,并调用了线程中的run方法。

下面就这两种方法进行解析:

2. start()方法

执行start方法是请求 JVM 开辟一个子线程,但线程具体什么时候开始执行,取决于CPU的调度,有可能立即执行,也有可能因为繁忙稍后执行或不执行。

start方法的执行流程:

  • 检查线程状态,只有NEW状态下的线程才能继续,否则会抛出IllegalThreadStateException(在运行中或者已结束的线程,都不能再次启动)
  • 加入线程组
  • 调用 start0() 方法启动线程,这是一个 native 方法,用 c++ 写的。

注意点:

  • start方法是被synchronized修饰的方法,可以保证线程安全;
  • start()方法不能执行两次,因为会有“检查线程状态”这一步,见上图;
  • 由JVM创建的main方法线程和system组线程,并不会通过start来启动。

3. run()方法

源码:

run()方法重写的两种方式:

  • 实现Runnable接口,重写Runnable接口的run()方法,注入到Thread类,执行target.run()。
  • 继承Thread类,重写Thread类中的run(),这时原run()中的if(target != null){ target.run(); },这部分就会被覆盖重写。

文章来源:深度解析多线程的创建方式和正确启动多线程


微信公众号名称:Java知者

微信公众号id:JavaZhiZhe

欢迎关注,谢谢!


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

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

相关文章

移动端浏览器性能优化探索

在移动端的页面开发过程中,我们经常提及页面性能优化、消除页面卡顿的话题,如何确定优化策略,我们首先应当对页面卡顿的行为有所认知。 前言  页面的卡顿现象可以比较明确的分为三个类型,分别是 “画面撕裂” 、“丢帧…

让你不再好奇怎么给小说配音

你是否曾经想象过,当你在读小说时,你可以听到人物的声音,感受到情感和气氛的变化?有声书的出现已经让这一切成为可能。然而,如何为小说创造生动的配音效果却是一个需要仔细考虑的问题。如果你还不知道怎么给小说配音的…

酷开会员丨版权时代,酷开科技打造更多优质内容服务消费者

以版权产业为核心的文化产业,需要重视版权、鼓励创新,才能形成文化创新的环境与氛围,这也是版权时代的发展趋势。在版权时代,付费观看是基本意识,比如电视内容供应方提供了大量免费的资源,观众为观看更精良…

数据结构之二叉树的基本实现

在我们之前已经了解的堆这样的完全二叉树的实现,也对树型结构有了一些了解,那么今天我们来看看二叉树的一些性质。 因为二叉树是一种每个节点至多只有两个子树(即二叉树的每个节点的度不大于2),并且二叉树的子树有左右…

二、Django REST Framework (DRF)序列化反序列化数据校验

参考: DRF 官方文档: Serializers - Django REST framework中文站点 为什么要学DRF和什么是REST API | 大江狗的博客 上一章: 一、Django REST Framework (DRF)& RESTful 风格api_做测试的喵酱的博客-CSDN博客 下一章:…

【C++】类和对象(中上):类的六个默认成员函数——构造函数、析构函数、拷贝构造函数!

目录 前言: 一、类的默认成员函数: 二、构造函数: 1.特性: 构造函数调用规则: 1.无参数的构造函数(默认构造函数): 2.带参数的构造函数: 3.全缺省的构造函数&…

Qt编写视频监控系统75-计算实时码率并显示

一、前言 做监控摄像头的实时视频显示,一般还会要求统计实时码率显示在通道画面上,一个是为了测试下整个软件的性能,同时也看下当前到底是主码流还是子码流,设备到底是不是真的按照设定的码流大小来传输视频数据的。视频码率就是…

【Mysql】 数据类型

文章目录 【Mysql】 数据类型数据类型分类数值类型1. tinyint类型2. bit类型3. 小数类型 字符串类型1.char2.varchar3. 日期和时间类型4. enum 和 set 【Mysql】 数据类型 mysql中数据类型的作用: 约束操作者的行为更清晰的代码逻辑不同的功用 – 例如&#xff0c…

【JavaSE】Java基础语法(八)

文章目录 🍓1. 类和对象🍹🍹1.1 类和对象的关系🍹🍹1.2 类的定义 🍓2. 对象内存图🍹🍹2.1 单个对象内存图🍹🍹2.2 多个对象内存图2.3 多个对象指向相同内存图…

统计学_贾俊平——思考题第9章 分类数据分析

1.简述列联表的构造与列联表的分布。 答:列联表是将两个以上的变量进行交叉分类的频数分布表。 列联表的分布可以从两个方面看,一个是观察值的分布,又称为条件分布,每个具体的观察值就是条件频数;一个是期望…

【数据结构】树的认识

一个人的未来不是预测出来的,而是创造出来的。 -- 亚当詹姆斯目录 🍁前言: 🍀一.什么是树? 🍑二.树有什么用? ❤️1. 数据库 🧡2. 文件系统 &#x1…

chatgpt赋能python:PythonUSB摄像头-拍摄更美好的瞬间

Python USB摄像头 - 拍摄更美好的瞬间 在过去的几年中,摄影已经迅速成为了一种爆炸性的趋势。人们希望能够记录下人生中的美好瞬间,分享给全球的亲朋好友。而USB摄像头的普及与发展使得照片拍摄变得更加便利。而在这其中,Python也扮演了一个…

spingboot+jsp仓储型物流企业车辆运输管理系统

随着时代的进步,物流车辆运输行业也逐渐变得庞大起来。当然,物流车辆运输公司要想做大做强,就有必要有自己完整的一套物流车辆运输管理系统。这必将为物流管理公司提供规范化的管理模式,在各个部门之间有效的协调、合作过程中必将为物流车辆公司提供大量的客户生源,争取赢得最大…

6.4_7关键路径

上一节我们学的叫做AOV网(activity on vertex) 这一节我们是(activity on edge network) 顶点表示事件是一瞬间发生的事情。边上的权值表示完成该活动的开销。 AOE网中,有些事情是可以并行的。 前后活动之间存在依赖关系,我i们要知…

python绘制密度图

本期目录 1、绘图参数2、使用 matplotlib 库绘制密度图时常用的参数3、案例4、 运行结果python绘图往期系列文章目录 1、绘图参数 可以使用多种库来绘制密度图,其中最常用的是 seaborn 和 matplotlib。以下是使用 seaborn 库绘制密度图时常用的参数: i…

简历上,我写精通 JUC 的底气

真的假的,你简历上敢写精通 JUC ? 是真学到精通了,还是说只学到了个皮毛就写精通,从而争取一个面试机会。 我相信,当很多人看到文章标题的第一反应也会如上面的一样,质疑、好奇。这很正常,如果…

手把手教你用Python调用彩云机器翻译API

一、引言 彩云这个小而美的机器翻译一直很低调,它让人眼前一亮的是之前我们分享的网页翻译插件,可以把外文网站翻译成英中对照的样式,便于我们学习。之前我们也写过文章介绍过: PythonFan:如何用Google翻译英文网页成…

c++学习——类和对象

类和对象的基本概念 类是自定义数据类型&#xff0c;是C语言的结构体进化而成的 对象是类实例化出的&#xff0c;用数据类型定义一个变量 #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std;class Maker //这个是类 { public:int a;//成员属性…

PostgreSQL EDB 公司推出新服务,ORACLE 平移到 POSTGRESQL 一体化服务

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

创建本地LocalHost-SSL证书

mkcert 使用方法 mkcert 是一个开源工具&#xff0c;用于快速生成有效的本地开发证书。它可以帮助开发人员在本地环境中使用 HTTPS 加密来模拟真实的生产环境。 安装 首先&#xff0c;你需要安装 mkcert 工具。以下是在常见操作系统上安装的命令&#xff1a; macOS 使用 Homebr…