Java 多线程系列Ⅰ(创建线程+查看线程+Thread方法+线程状态)

news2024/11/20 2:40:34

多线程基础

  • 一、创建线程的五种方法
    • 前置知识
    • 1、方法一:使用继承Thread类,重写run方法
    • 2、方法二:实现Runnable接口,重写run方法
    • 3、方法三:继承Thread,使用匿名内部类
    • 4、方法四:实现Runnable,使用匿名内部类
    • 5、方法五:使用lambda表达式(常用)
  • 二、体验多线程
    • 查看线程详情
  • 三、Thread及常见方法
    • 1、构造方法
    • 2、线程属性获取方法
    • 3、启动线程-start()
    • 4、中断一个线程:(让一个线程停下来)
    • 5、等待一个线程-join()
  • 四、线程的状态

一、创建线程的五种方法

前置知识

  1. Thread 类是用于创建和操作线程的类。每个线程都必须通过 Thread 类的构造方法创建,并实现 run() 方法来执行线程的任务。
  2. run() 方法是 Thread 类中用于定义线程要执行的任务的方法。当一个线程被启动后,它会调用自己的 run() 方法,在该方法中执行线程的任务逻辑。
  3. 需要注意的是,直接调用 run() 方法并不会启动一个新的线程,而只会在当前线程中依次执行 run() 方法中的代码。如果要启动一个新的线程并执行 run() 方法中的代码,应该使用 start() 方法来启动线程。

1、方法一:使用继承Thread类,重写run方法

class MyThread extends Thread {
    //run是线程的入口方法
    @Override
    public void run() {
        System.out.println("Hello t");
    }
}

public class ThreadDemo1 {
    //这种方式是使用Thread 的run来描述线程入口
    public static void main(String[] args) throws InterruptedException {
    	// Thread通过接收重写Thread内部run方法的子类
        Thread t = new MyThread();
        // start 启动线程
        t.start();
    }
}

2、方法二:实现Runnable接口,重写run方法

在Java中,Runnable是一个函数式接口(Functional Interface),用于表示要在一个线程中执行的任务。

class MyRunnable implements Runnable {

    @Override
    public void run() {
    	System.out.println("hello t");
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
    	// 1.先实例化实现了Runnable接口的类
        MyRunnable runnable = new MyRunnable();
        // 2.通过Thread的构造方法,传入runnable任务,创建线程
        Thread t = new Thread(runnable);
        
        t.start();
    }
}

3、方法三:继承Thread,使用匿名内部类

public class ThreadDemo3 {
    public static void main(String[] args) {
    	// 此处的new Thread(){...};就相当于一个继承了Thread类的子类
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello t");
            }
        };

        t.start();
    }
}

4、方法四:实现Runnable,使用匿名内部类

public class ThreadDemo4 {
    public static void main(String[] args) {
    	// 此处的new Runnable(){...}就相当于一个实现了Runnable接口的类
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello t");
            }
        });

        t.start();
    }
}

5、方法五:使用lambda表达式(常用)

虽然上面四种方式都可以达到创建线程的目的,但都不是常用的写法,推荐使用 lambda 表达式是最简单最直观的写法!

回顾lambda表达式

提到 lambda 表达式,下面我们在来回顾一下:

lambda 表达式,本质上就是一个匿名函数。(Java里面,函数(方法)是无法脱离类的,在Java里面 lambda 就相当于是一个例外,它可以将一个函数(或者说方法)作为参数传递到另一个方法中,而不需要将它包含在一个类中。)

虽然说,lambda 表达式可以在⼀定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简洁实现的。lambda 表达式毕竟只是⼀个匿名方法。当实现的接口中的方法过多或者多少的时候,lambda表达式都是不适用的。lambda 表达式,只能实现函数式接口

函数式接口:如果说,⼀个接口中,要求实现类必须实现的抽象方法,有且只有⼀个,这样的接口,就是函数式接口。

语法规则:

interface Demo {
    public void test();
}
public class Test {
    public static void main(String[] args) {
        // 使用lambda表达式实现接口
        Demo demo = () -> {
            System.out.println("test");
        };
        demo.test();
    }
}

其他规定

  1. ()里面放参数,如果只有一个参数,可以省略 ()
  2. {}里面放函数体,如果只有一行代码,也可以省略 {}
  3. 变量捕获”,lambda 表达式要想访问外面的局部变量,java 要求变量必须是 final 或是 “等效final”(即变量中没有用final修饰,但是代码中并没有做出修改)

使用 lambda 创建线程

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
    	// lambda本质上是实现了Runnable接口
        Thread t = new Thread(()->{
            System.out.println("hello t");
        });

        t.start();
    }
}

二、体验多线程

运行以下代码体验以下多线程情况下的代码执行。

下面代码中用到了sleep 方法,关于sleep方法说明:

在 Java 中,Thread 类中的 sleep(long millis) 方法用于使当前线程进入休眠状态,暂停执行一段时间。该方法接受一个以毫秒为单位的时间参数,表示要休眠的时间长度。

当线程处于休眠状态时,它不会占用CPU资源,也不会执行任何代码,直到休眠时间结束。在休眠期间,线程可以被中断(通过调用 interrupt() 方法),或者其他线程可以抢占 CPU 资源,使得该线程处于等待状态。

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("hello t");
            }
        },"t");

        t.start();
        while (true) {
            Thread.sleep(1000);
            System.out.println("hello main");
        }
    }
}

  1. 上述代码涉及两个线程:(1) main 方法所对应的线程(一个进程中至少有一个线程)也可称为主线程。(2) t 线程
  2. 运行程序,其实就是idea对应的进程创建了一个新的 java进程,这个java进程用来执行自己写的代码。同时这个 java进程里有两个线程,一个是main,一个是t,每个线程都是一个独立的执行流。此时的 hello t 是由 t 线程执行的打印逻辑。
  3. 这里的交替打印并不是严格意义上的交替,每一秒过后,先打印main还是先打印 t 是不确定的,因为多个线程在 CPU 上调度执行的顺序是不确定的(随机的)。

查看线程详情

我们可以使用 jdk 提供的第三方工具,查看java进程里面的线程详情:


注意事项:

  1. jconsole 只能分析 Java 进程。
  2. 运行就 jconsole,如果进程列表为空,可以尝试以管理员身份运行。

点进当前代码的进程对应的线程:


三、Thread及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联,即Java代码中的Thread对象和操作系统中的线程是一一对应的。而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

1、构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名

注意:name 名字参数,是给线程起了个名字,这里的名字不影响程序的执行,只是方便我们在调试的时候,快速找到需要的线程。

2、线程属性获取方法

返回值类型方法名说明
longgetId()返回线程标识符Id
StringgetName()返回线程名称
Thread.StategetState()返回线程状态
intgetPriority()返回线程优先级
booleanisDaemon()判断是否为后台线程
booleanisAlive()判断线程是否存活
booleanisInterrupted()判断线程是否被中断

(1)isDaemon()-前台线程后台线程说明:

  1. isDaemon()返回true-表示后台线程,后台线程不阻止Java进程结束,哪怕后台线程还没执行完,Java进程该结束就结束。
  2. isDaemon()返回false-表示前台线程,前台线程会阻止Java进程结束,必须得Java进程中所有的前台线程执行完Java进程才能结束。

    注:创建的线程默认是前台的,可以通过setDaemon(true)设置成后台的。

(2)isAlive()线程存活说明

描述的是系统内核里哪个线程是否存活,也就是说只有调用start()方法(调用 start 方法,
才真的在操作系统的底层创建出一个线程),启动线程之后,当线程正在执行时返回true,否则返回false

(3)isInterrupted()后面详细介绍…

3、启动线程-start()

上面介绍的五种创建线程的方式,都是通过覆写 run() 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。只有调用了start()方法,才真正从系统这里创建一个线程。

调用 start 方法, 才真的在操作系统的底层创建出一个线程

4、中断一个线程:(让一个线程停下来)

中断一个线程,就是让一个线程停下来,即线程终止,本质上来说,线程终止就是让该线程的入口方法执行完毕。这里的执行完毕可以是 return 返回代码执行完毕抛出异常 等情况。具体来说,可以采取如下策略:

(1)给线程设置一个结束标志位
例如:设置标志位isQuite作为线程结束的标志

public class ThreadExample_Interrupted {
    // 由于"变量捕获",这里将isQuite设置为成员变量
    public static boolean isQuite = false;

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (!isQuite) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t线程终止");
        });

        t.start();

        // 3秒后,在主线程中修改isQuite
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuite = true;

    }
}

注意:这种情况下是将isQuite设置成了成员变量,如果将其设置成局部变量,正常情况下由于线程和线程之间共用一个内存地址空间,语法上是成立的,但是对于lambda表达式要是想要访问外面的局部变量,这时就涉及到了Java变量捕获,即捕获的变量必须是 final 或者 “等效final”,即变量中没有用final修饰,但是代码中并没有做出修改。

(2)使用Thread类内置的标志位

方法名称说明
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
public void interrupt()中断对象关联的线程。如果线程正在阻塞,则以异常方式通知,否则设置标志位true。
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位。
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位。

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记。根据上述提供的方法,我们可以使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位。

例如:还是上面的例子,这次我们使用内置标志位

public class ThreadExample_Interrupted2 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{

            // 此处 currentThread currentThread 是获取到当前线程实例 t
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 将内部的标志位设置成true
        t.interrupt();
    }
}

注意:通过上面结果,我们看到,即使sleep被强制唤醒后,触发了两件事:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException异常的形式通知.
  2. 清除中断标志.

因此我们会看到,抛出异常后,t 线程中的循环继续进行(因为此时sleep被唤醒后清空了标志位 true->false)

对于 interrupt 只是通知不是命令,至于为什么 Java 不强制设置成“命令结束”的操作,主要是因为,这种强制性的设定是非常不友好的,对于线程 线程何时结束,始终是线程本身最清楚,所以还是交给线程自身来决定比较好。

5、等待一个线程-join()

线程之间是并发执行的,操作系统对于线程的调度是无序的,无法判断两个线程谁先执行结束,谁后执行结束。然而有时候有需要明确规定线程的结束顺序,这时就可以使用线程等待-join来实现。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒。

例如:我们需要等待一个线程t完成它打印工作后,才能进行main线程的打印工作。

public class ThreadExample_join {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello t");
            }
        });

        t.start();
        // 正常情况下,如果不加join,大部分情况下是先打印hello main(因为创建线程也是需要开销的)
        t.join();//这里就使t线程先执行完,main暂时阻塞
        System.out.println("hello main");
    }
}

上述代码在main线程中调用 t.join:如果 t 线程还没结束,main 线程就会“阻塞”等待-Blocking。也就是说代码执行到 t.join 时就停下来了,当前这个线程暂时不参与CPU的调度执行了。直到 t 线程执行完毕,此时 main 解除阻塞后将继续向下执行。


四、线程的状态

操作系统中的线程,自身是有一个状态的,但是Java 中Thread是对系统线程的封装,将里面的状态进一步精细化了。

  • NEW :系统中的线程还没创建出来,但是有个 Thread 对象
  • RUNNABLE: 就绪状态(1.正在CPU上执行 2.准备好随时可以去CPU上运行)
  • TERMINATED: 系统中的线程已经执行完了,Thread对象还在
  • TIMED_WAITING: 指定时间等待
  • BLOCKED :等待锁出现的状态
  • WAITING :使用 wait、join 方法出现的状态

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

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

相关文章

如何在,Linux中安装Luajit2.*

1.文件下载The LuaJIT Project 2.将下载文件上传到对应的服务器&#xff1a;例如/opt 3.进入对应的文件夹 4.make PREFIX/usr/local&#xff0c;设置安装路径 5.make install&#xff0c;编译安装 6.进入安装目录&#xff0c;cd /usr/local/include/luajit-2.0 7.luajit -v…

SAP FI之定义财务年和财务年度变式(Fiscal Year Variants)

目录 前言 一、财务年度/财务年度变式 二、使用步骤 1.配置步骤 前言 本文主要介绍SAP会计年度和SAP会计年度变式。 一、财务年度/财务年度变式 财务年度可以具有与日历年相同的期间&#xff0c;也可以不同。中国财政年度从1月到12月&#xff0c;称为历年制&#xff0c;有…

基于沙猫群算法优化的BP神经网络(预测应用) - 附代码

基于沙猫群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于沙猫群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.沙猫群优化BP神经网络2.1 BP神经网络参数设置2.2 沙猫群算法应用 4.测试结果&#xff1a;5.Matlab代…

WebGL 同一缓冲区多种数据传入顶点着色器 gl.vertexAttribPointer()的步进和偏移参数

目录 目录 为了将顶点坐标传入着色器&#xff0c;需要遵循一下五步&#xff1a; 但是&#xff01;&#xff01;&#xff01; 示例代码&#xff1a; gl.vertexAttribPointer&#xff08;&#xff09;的函数规范 stride参数 顶点坐标数据 offset参数 顶点尺寸数据 首先…

KaiwuDB 助力能源企业实现 4 大价值提升

行业背景 近年来&#xff0c;随着能源行业数字化的不断推进&#xff0c;智能电网、可再生能源发电、分布式发电、微电网等技术蓬勃发展。越来越多的能源企业意识到数据管理与价值挖掘对储能及能源利用有着重大意义&#xff0c;并开始探索一套有效的数据库解决方案以应对分布式…

Chrome小恐龙快跑小游戏——Python实现

目录 视频演示 代码实现 视频演示 Chrome小恐龙快跑小游戏——Python实现 代码实现 import pygame import os import random pygame.init()# Global Constants SCREEN_HEIGHT 600 SCREEN_WIDTH 1100 game_over False SCREEN pygame.display.set_mode((SCREEN_WIDTH, SCR…

电子词典dictionary

一、项目要求&#xff1a; 1.登录注册功能&#xff0c;不能重复登录&#xff0c;重复注册。用户信息也存储在数据库中。 2.单词查询功能 3.历史记录功能&#xff0c;存储单词&#xff0c;意思&#xff0c;以及查询时间&#xff0c;存储在数据库 4.基于TCP&#xff0c;支持多客户…

三、mycat分库分表

第五章 分库分表 一个数据库由很多表的构成&#xff0c;每个表对应着不同的业务&#xff0c;垂直切分是指按照业 务将表进行分类&#xff0c;分布到不同 的数据库上面&#xff0c;这样也就将数据或者说压力分担到不同 的库上面&#xff0c;如下图&#xff1a; 系统被切分成了&…

three.js(四):react + three.js

绘制多个立方体 1.搭建reactts 项目 npx create-react-app basics-demo --template typescriptreactts 的用法可参考此链接&#xff1a; https://react-typescript-cheatsheet.netlify.app/docs/basic/setup 2.安装three依赖 npm install three types/three --save3.安装路…

200 套基于Java开发的Java毕业设计实战项目(含源码+说明文档)

文章目录 简介前言第一部分第二部分部分截图源码咨询 简介 博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 前言 对于java方向的毕业设计题目选题&#xf…

Weblogic漏洞(四)之 CVE-2018-2894 任意文件上传漏洞

CVE-2018-2894 任意文件上传漏洞 漏洞影响 Weblogic受影响的版本&#xff1a; 10.3.6.012.1.3.012.2.1.212.2.1.3 漏洞环境 此次我们使用的是vnlhub靶场搭建的环境&#xff0c;是vnlhub中的Weblogic漏洞中的CVE-2018-2894靶场&#xff0c;我们 cd 到 CVE-2018-2894&#x…

R3LIVE源码解析(6) — R3LIVE流程详解

目录 1 R3LIVE框架简介 2 R3LIVE的launch文件 3 R3LIVE的r3live_config文件 4 R3LIVE从哪开始阅读 1 R3LIVE框架简介 R3LIVE是香港大学Mars实验室提出的一种融合imu、相机、激光的SLAM方法&#xff0c;R3LIVE由两个子系统组成&#xff0c;一个激光惯性里程计&#xff08;L…

【无标题】8.31在华清

可以登录但是不能跳转

怎么用postman连接websocket

点击右侧栏的Collections&#xff0c;然后点击旁边的New&#xff0c;然后点击其中的WebSocket Request,然后输入Url&#xff0c;点击Connection&#xff0c;这里需要注意的是Url不能加上http://&#xff0c;因为这个不是http协议。

23062网络编程day5

根据select TCP服务器流程图编写服务器 #include <myhead.h> #define ERR_MSG(msg) do{\fprintf(stderr,"__%d__:",__LINE__);\perror(msg);\ }while(0)#define PORT 8888 #define IP "192.168.114.104"int keyboard_events(void); int cliConnect_…

美国纽约10日游

一、前言 我有两周断更了&#xff0c;原因是去纽约只顾着玩&#xff0c;没时间写&#xff0c;今天有时间正好和大家分享一下去纽约的攻略 二、以下是一个10天去美国纽约旅游的攻略&#xff0c;十万以内&#xff0c;包括机票、酒店、交通、餐饮和景点门票等费用&#xff1a; 第…

Leetcode ->206 反转链表

题目 算法思路及代码实现 #include <iostream> using namespace std;struct ListNode {int val; //当前节点的值ListNode *next;

目标检测笔记(十二):如何通过界面化操作YOLOv5完成数据集的自动标注

文章目录 一、意义二、修改源码获取三、自动标注前期准备四、开始自动标注五、可视化标注效果六、XML转换TXT 一、意义 通过界面化操作YOLOv5完成数据集的自动标注的意义在于简化数据标注的流程&#xff0c;提高标注的效率和准确性。 传统的数据集标注通常需要手动绘制边界框…

通信笔记:RSRP、RSRQ、RSNNR

0 基础概念&#xff1a;RE、RS和RB RE (Resource Element)&#xff1a;资源元素是 LTE 和 5G 网络中的最小物理资源单位。一个资源元素对应于一个子载波的一个符号周期。 RS (Reference Signal)&#xff1a;参考信号是在 LTE 和 5G 网络中用于多种目的的特定类型的信号。它们可…

PyCharm切换虚拟环境

PyCharm切换虚拟环境 为了满足不同任务需要不同版本的包&#xff0c;可以在Anaconda或者Miniconda创建多个虚拟环境文件夹&#xff0c;并在PyCharm下切换虚拟环境。 解决方案 1、打开Ananconda Prompt 2、创建自己的虚拟环境 格式&#xff1a;conda create -n 虚拟环境名字…