【JavaEE精炼宝库】多线程1(认识线程 | 创建线程 | Thread 类)

news2024/12/27 13:42:13

目录

一、认识线程

1.1 线程的概念:

1.2 为什么需要线程:

1.3 面试题.谈谈进程和线程的区别:

1.4 Java的线程和操作系统线程的关系:

二、创建线程

2.1 创建线程的5种写法:

2.1.1 写法1.继承 Thread 类:

2.1.2 写法2.实现 Runnable 接口:

2.1.3 写法3.继承 Thread 使用匿名内部类:

2.1.4 写法4.实现 Runnable 使用匿名内部类:

2.1.5 写法5.使用lambda(推荐写法):

2.2 run方法和start方法的区别:

2.3 多线程的优势:

三、Thread类及常见方法

3.1 Thread 的常见构造方法:

3.2 jconsole使用过程:

3.3 Thread的常见属性:

3.3.1 属性列表:

3.3.2 前后台线程的关系:


一、认识线程

1.1 线程的概念:

一个线程就是一个 "执行流" 。每个线程之间都可以按照顺序执行自己的代码。多个线程之间 "同时" 执行着多份代码。

1.2 为什么需要线程:

(1)首先经过多年的发展,“并发编程” 已成成为 “刚需”。

• 单核CPU的发展遇到了瓶颈.要想提高算力,就需要多核CPU.而并发编程能更充分利用多核CPU资源。

 • 有些任务场景需要 "等待IO",为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。

(2)其次,虽然多进程也能实现并发编程,但是线程比进程更轻量。

• 创建线程比创建进程更快。

• 销毁线程比销毁进程更快。

• 调度线程比调度进程更快。

(3)最后线程虽然比进程轻量,但是人们还不满足,于是又有了 "线程池" (ThreadPool)和 "协程"  (Coroutine)。

本文章主要介绍多线程,有关线程池和协程的概念后续会单独再写文章解释。

1.3 面试题.谈谈进程和线程的区别:

主要有如下四点:

• 进程是包含线程的。每个进程至少有⼀个线程存在,即主线程。

• 进程和进程之间不共享内存空间。同⼀个进程的线程之间共享同⼀个内存空间。

• 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

• 一个进程挂了一般不会影响到其他进程。但是一个线程挂了,可能把同进程内的其他线程一起带走(整个进程崩溃)。

1.4 Java的线程和操作系统线程的关系:

线程是操作系统中的概念。操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用(例如Linux的pthread库)。

Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。

二、创建线程

下面我会提供创建线程的常见的5中写法,希望友友们都要掌握。下面经常会用到run方法和start方法,关于它们的区别,大家可以先把这5中写法看完后,我在后面有写区别🤩🤩🤩。

2.1 创建线程的5种写法:

2.1.1 写法1.继承 Thread 类:

继承 Thread 来创建⼀个线程类。写法如下:

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello Thread");
        System.out.println("Thread end");
    }
}
public class demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();//向上转型
        t.start();//启动线程
    }
}

这里解释一下为什么不能直接直接创建一个Thread对象,而是要再写一个Thread的子类:这是因为我们要重写 run 方法,如果不重写,直接调用原生的,会达不到我们的预期,这显然不是我们想看到的。

运行结果:

2.1.2 写法2.实现 Runnable 接口:

• Runnable接口源码:

通过观察其源码我们不难发现这是一个 “函数式接口” ,所以我们后面有一种写法就会利用到lambda表达式,里面涉及到的一些 “变量捕获” 的知识如果友友忘了的话要记得复习呀。

这个相比于第一个写法的好处是:能够起到解耦合的作用,例如当前是通过多线程的方式执行的,未来也可以很方便改成基于线程池的方式执行,也可以改成基于虚拟线程的方式执行(改动成本比较小),而继承Thread的写法基本就只适用于多线程。具体写法如下:

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello Thread");
        System.out.println("Thread end");
    }
}
public class demo2 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

运行结果: 

2.1.3 写法3.继承 Thread 使用匿名内部类:

这个写法的效果和 2.1 的写法效果没有任何区别,因为使用匿名内部类本来就是为了方便。具体写法如下:

public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello Thread");
                System.out.println("Thread end");
            }
        };
        t.start();
    }
}

运行结果和前面一样就不贴了。

2.1.4 写法4.实现 Runnable 使用匿名内部类:

和写法2效果一样:

public class demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable(){
            @Override
            public void run() {
                System.out.println("hello Thread");
                System.out.println("Thread end");
            }
        });
        t.start();
    }
}

2.1.5 写法5.使用lambda(推荐写法):

这个是比较推荐的写法,因为是 “函数式接口” 我们就可以使用lambda表达式来简化写法。具体写法如下:

public class demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello Thread");
            System.out.println("Thread end");
        });
        t.start();
    }
}

 上述 5 种写法本质都是要把线程执行的任务内容表示出来,通过 Thread 的 start 来创建 / 启动系统中的线程。Thread 对象和操作系统内核中的线程是一一对应的关系。 

2.2 run方法和start方法的区别:

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

• 覆写 run 方法是提供给线程要做的事情的指令清单。

• 线程对象可以认为是把 李四、王五叫过来了。

• 而调用 start() 方法,就是喊⼀声:”行动起来!“,线程才真正独立去执行了。

总而言之:调用 start 方法,才真的在操作系统的底层创建出一个线程。

2.3 多线程的优势:

利用多线程在一些场合可以提高程序的运行速度。具体案例如下:

前置知识:

• 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳。

• serial 串行的完成一系列运算。concurrency 使用两个线程并行的完成同样的运算。

如果对串行和并行不了解的话可以前往:JavaEE前置知识 中查看并行与并发的区别。

public class ThreadAdvantage {
    // 多线程并不⼀定就能提⾼速度,可以观察,count 不同,实际的运⾏效果也是不同的
    private static final long count = 10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
        // 使⽤并发⽅式
        concurrency();
        // 使⽤串⾏⽅式
        serial();
    }
    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();

        // 利⽤⼀个线程计算 a 的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
                }
            }
        });
        thread.start();
        // 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        // 等待 thread 线程运⾏结束
        thread.join();

        // 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发: %f 毫秒%n", ms);
    }
    private static void serial() {
        // 全部在主线程内计算 a、b 的值
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
        }
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串⾏: %f 毫秒%n", ms);
    }
}

案例结果如下:

可以看到速度快了两倍多。不使用多线程的并发就会出现 “一核有难,多核围观” 的现象。

三、Thread类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。

用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

3.1 Thread 的常见构造方法:

我们最常使用的是第三个,至于第五个目前在实际开发中更多的是被线程池取代了,这里只演示第三个。演示如下:

public class demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"我的Thread");
        t.start();
        while(true){
            System.out.println("hello Main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

我们可以利用使用 jconsole 命令观察线程。

3.2 jconsole使用过程:

我们利用上面的程序来进行连接,连接的时候要保证程序在运行中。

• 打开 c 盘并进入 Program Files :

• 进入 Java 中的 jdk :

• 进入 bin 后找到 jconsole 后以管理员的身份运行它:

•  看到这个就成功找到 jconsole 了:

记得连接我们运行的java程序。

注意:程序一定要保证在运行状态,比如我们写一个while(true)循环来保证我们连接的时候程序在跑,不然我们是连接不到的。

• 查看结果:

点击线程,在下面我们能看到main和我的Thread(修改命名)。

完成上面步骤我们已经成功利用 jconsole 查看运行的 java 线程。

3.3 Thread的常见属性:

3.3.1 属性列表:

属性解释:

• ID 是线程的唯一标识,不同线程不会重复。

• 名称是各种调试工具用到。

• 状态表示线程当前所处的一个情况。

• 优先级高的线程理论上来说更容易被调度到。

• 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

• 是否存活,即简单的理解,为 run 方法是否运行结束了。

• 线程的中断问题,下面会单独讲。

3.3.2 前后台线程的关系:

• 前台线程:前台线程如果不运行结束的话,此时 Java 进程是一定不会结束的。

• 后台线程:后台线程即使继续在执行,也不能阻止 Java 进程结束。

我们默认创建的线程都是前台线程。我们可以利用 setDaemon 方法来把线程设置为后台线程。

注意:关于线程的各种属性的设置,都要放在 start 之前,一旦线程已经启动了,那么开弓就没有回头箭,这个时候再设置就来不及了,还会返回一个异常。

测试案例:

public class demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.setDaemon(true);//把t设置为后台线程
        t.start();//启动线程
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Main end");
    }
}

友友们可以把这个代码贴到自己的编译器上面,看看有没有 setDaemon 的区别。

案例效果如下:

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

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

相关文章

JS实现初始化、动态点击切换激活样式

食用须知&#xff0c;不懂得把代码交给AI解释一下&#xff0c;明白流程就会用了&#xff0c;本文只有js与html&#xff0c;样式代码一概没有&#xff1a; 效果展示 1、点击显示的盒子代码 <div data-v-e1dd37c4"" class"news-container main-width-contain…

uniapp 版本检查更新

总体来说uniapp的跨平台还是很不错的&#xff0c;虽然里面各种坑要去踩&#xff0c;但是踩坑也是开发人员的必修课和成长路。 这不&#xff0c;今天就来研究了一下版本检查更新就踩到坑了。。。先来看看检查更新及下载、安装的实现。 先来看看页面&#xff1a; 从左到右依次为…

【Linux系统编程】第十六弹---冯诺依曼体系结构与操作系统

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、冯诺依曼体系结构 2、操作系统原理 2.1、什么是操作系统&#xff1f; 2.2、用图解释操作系统 2.3、理解操作系统 总结 …

瞪羚企业申报条件!武汉市瞪羚企业奖励补助

武汉市江岸区、江汉区、硚口区、汉阳区、武昌区、青山区、洪山区、蔡甸区、江夏区、黄陂区、新洲区、东西湖区、汉南区企业申报瞪羚企业的条件是什么&#xff1f;各区对瞪羚企业的奖励补助有哪些&#xff1f; 武汉市瞪羚企业申报条件&#xff1a; 1、符合武汉市光电子信息、生物…

Windows Server 2012 R2 新增D盘分区

我们经常搭建windows版本的游戏时会要在D盘上操作&#xff0c;今天就介绍下新的服务器如何新增一个D盘。 在"开始"图标右边有个”服务器管理器“&#xff0c;单击点开 点开服务器管理器后&#xff0c;点击“工具”打开“计算机管理” 打开计算机管理后点击“存储”-…

发票审核如何自查?报销没有发票,如何处理?

在财务管理中&#xff0c;发票是非常重要的一项凭证&#xff0c;是费用核算和税务申报的重要依据&#xff0c;但光靠发票入账可能会被定义为虚开。 一、费用报销审核必看的6个要点 1、票据与实际业务吻合 这是费用报销中最基本的常识&#xff0c;比如&#xff1a;采购一批物料&…

T型三电平逆变器的Simulink仿真

1 T型三电平拓扑的开关状态 图1为T字型-三电平电路单相拓扑&#xff0c;拓扑中共有4个IGBT&#xff0c;4个二极管&#xff0c;还有电容组C1和C2&#xff1b;假设正负母线电压均等&#xff0c;都是Vdc。将T1&#xff0c;T2&#xff0c;T3&#xff0c;T4的状态用1和0分别表示&…

51单片机入门:串口通信

串行通信的初步认识 通信方式分类 1、按照数据传送方式&#xff1a; 并行通信&#xff1a;通信时数据的各个位同时传送&#xff0c;可以实现字节为单位的通信。 但是通信线多&#xff0c;占用资源多&#xff0c;成本高。 串行通信&#xff1a;一次只能发送一位&#xff0c…

Linux:进程概念(三.详解进程:进程状态、优先级、进程切换与调度)

上次讲了进程这些内容&#xff1a;Linux&#xff1a;进程概念&#xff08;二.查看进程、父进程与子进程、进程状态详解&#xff09; 文章目录 1.Linux中的进程状态1.1前台进程和后台进程运行状态睡眠状态磁盘休眠状态停止状态kill指令—向进程发送信号 死亡状态 2.僵尸进程2.1僵…

零售全渠道营销业务链分析,让企业管控能力大幅加强!

对于传统的、规模化的零售快消企业来讲&#xff0c;面临着很大的渠道管理和建设问题&#xff0c;如何尽快实现整个营销体系的全渠道数字化转型是当务之急、重中之重。 面对错综分散的经销商&#xff0c;零售快消企业订货流程会越复杂&#xff0c;加之对门店管理较为粗放&#…

武汉星起航:亚马逊母亲节选品指南,热销产品助卖家赢取节日商机

随着母亲节的脚步日益临近&#xff0c;全球消费者纷纷开始为这一特殊的节日挑选礼物&#xff0c;以表达对母亲的深深感激和无尽爱意。作为跨境电商的重要平台&#xff0c;亚马逊凭借其丰富的商品种类和便捷的购物体验&#xff0c;成为消费者选购母亲节礼物的首选之地。那么&…

【数据结构陈越版笔记】第1章 概论

我最近准备以陈姥姥的数据结构教材为蓝本重新学一下数据结构&#xff0c;写一下读书笔记 第1章 概论 1.1 引子 概论中首先描述了&#xff0c;数据结构的定义没有具体的定义&#xff0c;初学者可以不用管这个定义的问题&#xff0c;但是我理解的和维基百科的说法是一样的“数…

[已解决]ModuleNotFoundError: No module named ‘tqdm‘

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…

如何批量删除多个不同路径的文件但又保留文件夹呢

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 1、我准备了三个文件夹&#xff08;实际操作的时候可能是上百个文件夹&#xff0c;无所谓&#xff09;&#xff0c;里面都放了两个图片 2、然后打开工具&am…

RustDesk 自建服务器部署和使用教程

RustDesk 是一个强大的开源远程桌面软件&#xff0c;是中国开发者的作品&#xff0c;它使用 Rust 编程语言构建&#xff0c;提供安全、高效、跨平台的远程访问体验。可以说是目前全球最火的开源远程桌面软件了&#xff0c;GitHub 星星数量达到了惊人的 64k&#xff01; 与 Team…

AIGC|将GPTBots与10000+主流软件连接,实现应用场景全覆盖

一、自动化工作流的无限可能&#xff0c;由AI带来 当前市场上存在许多自动化工作流工具&#xff0c;这些工具在很大程度上提升了人们的工作效率&#xff0c;为企业节省了大量时间和人力成本。然而&#xff0c;这些工具并非万能&#xff0c;它们在实际应用中仍存在一定的局限性…

如何在自己的服务器上快速搭建第一个网站(其一)

根据上篇文章相信很多人以及成功搭建服务器啦。今天我们讲下如何在自己的服务器快速搭建第一个网站的一些重要配置&#xff0c;以及搭建网站的必备环境。干货满满&#xff0c;希望大家能够关注点赞收藏。 我会不定期更新一些实用的工具&#xff0c;欢迎大家私信评论喔&#xf…

工作中使用Optional处理空指针异常

工作中使用Optional处理空指针异常 实体类以前对空指针的判断Optional处理空指针测试结果 实体类 package po;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.io.Serializable;Data AllArgsConstructor NoArgsConstruct…

kafka生产者消费者举例

文章目录 kafka介绍生产者消费者例子一、生产者二、消费者三、效果 KafkaTemplate KafkaListener kafka介绍 Kafka 是一款分布式流处理平台&#xff0c;它被设计用于高吞吐量、持久性、分布式的数据流处理。 Kafka 简介&#xff1a; Kafka 是一个高吞吐、分布式、基于发布 订阅…

NSS刷题

[SWPUCTF 2021 新生赛]jicao 类型&#xff1a;PHP、代码审计、RCE 主要知识点&#xff1a;json_decode()函数 json_decode()&#xff1a;对JSON字符串解码&#xff0c;转换为php变量 用法&#xff1a; <?php $json {"ctf":"web","question"…