Java多线程学习——线程的创建、Thread类以及多线程状态

news2024/9/28 9:23:21

文章目录

  • 学习目标
  • 一、认识线程
    • 1、线程是什么?
    • 2、为什么要有线程
    • 3、==进程和线程的区别==
  • 二、Thread类以及常见方法
    • 1.创建线程的几种方式
    • 2、Thread类属性及方法
      • 2.1、Thread的常见构造方法
      • 2.2、Thread的常见属性
    • 3、线程的中断-interrupt()
        • 中断一个线程:
    • 4、等待一个线程-join()
  • 三、线程的状态
    • 1、线程的所有状态
    • 2、线程的状态转移
    • 3、Jconsole调试工具
  • 总结


学习目标

  • 认识多线程
  • 掌握多线程的创建
  • 掌握多线程的状态
  • 掌握什么是线程安全以及解决方法
  • 掌握synchronized、volatile关键字

提示:以下是本篇文章正文内容,下面案例可供参考

一、认识线程

1、线程是什么?

一个线程就是一个执行流。每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。举个例子:

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。
如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。

2、为什么要有线程

首先,“并发编程”成为“刚需”。

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源。
  • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程。

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

  • 创建线程比创建进程更快。
  • 销毁线程比销毁进程更快。
  • 调度线程比调度进程更快。

最后,线程虽然比进程轻量,但是还是不能将多核cpu的性能发挥到极致,于是又有了“线程池”和“协程”

3、进程和线程的区别

  • 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间。
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
    在这里插入图片描述
  • Java的线程和操作系统线程的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

二、Thread类以及常见方法

1.创建线程的几种方式

方法1 继承Thread类

class MyThread extends Thread {
     @Override//重写run方法
     public void run() {
         System.out.println("这里是线程运行的代码");
     }
}
public static void main(){
    MyThread t = new MyThread();
    t.start();//线程开始执行
}

方法2 实现Runnable接口

class MyRunnable implements Runnable{
    @Override//重写run方法
    public void run() { 
        System.out.println("my runnable");
    }
}
public class Demo {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

其他变形

/**
 * 匿名内部类的方式, 创建Thread子类
 */
public class Demo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println(4444);
            }
        };
        thread.start();
    }
}
-------------------------------------------------------------
/**
 * 匿名内部类的方式, 创建Runnable的子类
 */
public class Demo5 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(5555);
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
--------------------------------------------------------------------
/**
 * lambda表达式, 创建线程
 */
public class Demo6 {
    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            System.out.println(5555);
        });
        thread.start();
    }
}

2、Thread类属性及方法

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

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

2.1、Thread的常见构造方法

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

2.2、Thread的常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,下面会进一步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了

需要强调的是,通过覆写run()方法创建的是线程对象,但此时线程对象仅仅只是被创建出来了,只有调用了start()方法之后线程才真正独立去执行了,才真正的在操作系统的底层创建出一个线程。

以下是代码示例,演示线程的构造方法以及getName():

public class Demo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());

        Thread t2 = new Thread("一号线程"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println(this.getName());
            }
        };
        t2.start();

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
         //这里不能使用getName,因为Runnable没有name这个属性
         //    System.out.println(this.getName());
            }
        },"二号线程");
        t3.start();
    }
}

运行结果打印了主线程名称以及创建的两个线程(这里使用带名称参数的构造方法):

在这里插入图片描述

3、线程的中断-interrupt()

中断一个线程:

接着讲上面转账的例子,李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。

两种方式:

1.通过共享的标记进行沟通(volatile关键字)

public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;
        @Override
        public void run() {
            while (!isQuit) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        target.isQuit = true;
    }
}

2.调用interrupt()方法进行通知

Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位(使用最多)

使用 thread 对象的 interrupted() 方法通知线程结束:

public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
// 两种方法均可以
            while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            + ": 有内鬼,终止交易!");
// 注意此处的 break
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        thread.interrupt();
    }
}

thread收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志。
  • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程。
  1. 否则,只是内部的一个中断标志被设置,thread 可以通过
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志,这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

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

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + ": 我还在工作!");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
        };
        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
    }
}

此时运行程序,程序会像我们预想的那样 按部就班的工作。如图:

在这里插入图片描述
当我们注释掉那两行join后 运行结果如图:

在这里插入图片描述

解释一下为什么:

t1.join()后,main线程就必须等待t1执行完,才能接着往下执行,t1未执行完时,main线程阻塞,t2同理。 这里t2是在执行完t1后启动的。假如t1线程和t2线程同时启动,那么将这两个线程join后,main线程会阻塞的时间是这两个线程运行时间的最大值,即阻塞时是同时在等这两个线程执行完毕。

谁调用join() 谁阻塞,即join写在哪个线程内,该线程就阻塞。

三、线程的状态

1、线程的所有状态

线程的状态是一个枚举类型Thread.State

public class ThreadState {
	public static void main(String[] args) {
		for (Thread.State state : Thread.State.values()) {
				System.out.println(state);
			}
	}
}
  • NEW: 安排了工作, 还未开始行动
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 这几个都表示排队等着其他事情
  • WAITING: 这几个都表示排队等着其他事情
  • TIMED_WAITING: 这几个都表示排队等着其他事情
  • TERMINATED: 工作完成了。

2、线程的状态转移

在这里插入图片描述

还是我们之前的例子:

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;
当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;

当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入BLOCKED 、 WATING 、 TIMED_WAITING 状态,至于这些状态的细分,我们以后再详解;

如果李四、王五已经忙完,为 TERMINATED 状态。

所以,前面提到的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。

在这里插入图片描述

在具体的代码中,线程的状态是如何进行转移的呢?

在这里插入图片描述
如图所示,

  • 创建Thread实例后,线程状态为New
  • 调用start方法后,线程变为Runnable状态,
  • 此时再调用wait()、join()等方法又会进入Waitting状态,
  • 当给wait()、sleep()、join()传入时间参数时进入的是Timed_Waitting状态,
  • 若线程需要资源竞争,比如CPU资源已被其他线程加锁,则进入Blocked状态。
  • run()方法执行结束,线程进入Terminated状态。
  • 除了New状态和Terminated状态 线程都属于存活状态。

在代码中通过isAlive方法判定线程的存活状态:

public class Demo19 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100_000_000; i++) {
            }
            System.out.println("线程执行结束");
        });
        System.out.println("start 方法之前:" + t1.getState());
        t1.start();
        System.out.println("start方法之后:"+ t1.getState());
        Thread.sleep(1000);
        System.out.println(t1.getState());
    }
}

在这里插入图片描述

3、Jconsole调试工具

在观察线程状态的具体转移过程时,我们可以使用JDK自带的调试工具Jconsole,通常在JDK的安装目录bin目录下,当我们运行一个多线程代码后 可以双击打开Jconsole查看各线程的状态。

在这里插入图片描述

在这里插入图片描述

点击Demo20 链接后可以看到该线程此时状态为Timed_Waitting,若将sleep内时间删除重新运行,状态则会变为Waitting状态。

在这里插入图片描述

我们通过加锁的方式,创建两个线程去竞争同一把锁,这样没有竞争到的就进入block状态。这里Thread-0是t1(因为默认从0号开始创建),此时Thread-1就没有竞争到锁进入Blocked状态,等待锁释放资源。

在这里插入图片描述

在这里插入图片描述
线程的六种状态及其转移过程如上,总结如下:

  • BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知
  • TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒
  • 只有在New (刚创建)和 Terminated(run方法执行结束)状态时,线程是“死”的。

总结

以上就是今天要讲的内容,本文算是多线程的入门吧,介绍了什么是线程、线程的创建、常见方法以及线程的状态和转移。后续将继续深入学习多线程以及JavaEE的其他内容,感兴趣的朋友可以点点订阅~

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

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

相关文章

前端面试题 —— 浏览器原理(一)

目录 一、进程与线程的概念 二、如何实现浏览器内多个标签页之间的通信? 三、浏览器资源缓存的位置有哪些&#xff1f; 四、对浏览器内核的理解 五、常见的浏览器内核比较 六、浏览器的主要组成部分 七、渲染过程中遇到 JS 文件如何处理&#xff1f; 八、什么情况会阻塞…

【C语言】动态内存管理

我们之前开辟的空间&#xff0c;大小固定&#xff0c;且在申明数组的时候&#xff0c;必须指定数组的长度。但是有时候我们需要的空间大小在程序运行的时候才知道&#xff0c;这就得动态内存开辟出马了。 目录 1.malloc和free 2.calloc 3.realloc 4.常见动态内存错误 5.经…

TCP 握手过程 三次 四次

蛋老师视频 SYN 同步 ACK 确认 FIN 结束 核心机制是确定哪些请求或响应需要丢弃 SYN、ACK、FIN 通过 1/0 设置开启/关闭 开启SYN后&#xff0c;报文中会随机生成 Sequence序号 用于校验 &#xff08;应用可能发起多个会话&#xff0c;可以区分&#xff09; 服务器的同步序…

2023版D盾防火墙v2.1.7.2,主动防御保护,以内外保护的方式 防止网站和服务器给入侵。限制了常见的入侵方法,让服务器更安全

v2.1.7.2 (20230107) 2023-1-7 1.修正PHP一处文件检测的bug。 2.修正某些情况下无法文件加白问题。 v2.1.7.2 2022-10-13 1.针对aspx的样本加入了新的识别。 2.针对上传 doc格式文件提示“上传格式不符” 的修正。 3.工具“HTTPS安全”,把 TLS 1.1 和 TLS 1.0 设置为默认不选中…

杰理AD16N简介

一、概述&#xff1a; AD16N是杰理新出的一个MP3解码芯片&#xff0c;是高集成度的 32 位通用音频 SOC&#xff0c; 集成 40KByte SRAM&#xff0c; 时钟源可选内部 RC 或外部12MHz 晶振&#xff0c; 最高主频可达 160MHz&#xff1b; 主要是替代AC109N系列和AC608N、AC104N系列…

Python爬虫书写时遇到的问题汇总

文章目录python的xpath插件需要的库下载出现问题懒加载python 爬取图片,网址都正确但是下不下来的原因:爬取下来的文字包含Windows不能识别的特殊字符selenium的find_element_by_id()出现的问题爬虫信息写入mysql时的1045号错误python的xpath插件需要的库下载出现问题 ERROR: C…

MySQL特殊语法insert into ... on duplicate key update ...

一、前言 在日常开发中&#xff0c;经常会遇到这样的需求&#xff1a;查看某条记录是否存在&#xff0c;不存在的话创建一条新记录&#xff0c;存在的话更新某些字段。 比如下列伪代码&#xff1a; $row mysql_query($result);if($row){mysql_execute(update ...);}else{my…

MongoDB复习

目录 1.docker安装 2.mondo概念解析 3.数据库操作 4.基本数据类型 5. 适合使用场景 6.对集合操作 7.常用操作 1.docker安装 docker pull mongo:latest docker run -d --restartalways -p 27017:27017 --name mymongo -v /data/db:/data/db -d mongo docker exec -it m…

【SpringBoot高级篇】SpringBoot集成Sharding-JDBC分库分表

【SpringBoot高级篇】SpringBoot集成Sharding-JDBC分库分表Apache ShardingSphere分库分表分库分表的方式垂直切分垂直分表垂直分库水平切分水平分库水平分表分库分表带来的问题分库分表中间件Sharding-JDBCsharding-jdbc实现水平分表sharding-jdbc实现水平分库sharding-jdbc实…

数据结构-考研难点代码突破(查找算法 - 散列表(哈希表)C++实现除留余数法拉链法哈希)

文章目录1. 哈希表与解决哈希冲突的方法2. C实现除留余数法拉链法哈希1. 哈希表与解决哈希冲突的方法 散列表(Hash Table)&#xff0c;又称哈希表。是一种数据结构。 特点&#xff1a;数据元素的关键字与其存储地址直接相关。 关键字通过散列函数&#xff08;哈希函数&#…

Vue3.0文档整理:2、创建单页面应用程序

2.1&#xff1a;创建步骤 2.1.1&#xff1a;vue-cli 安装并执行create-vue:npm init vuelatest 它是Vue官方的项目脚手架工具 选择项目功能 除了第一项的项目名字外&#xff0c;其他可以暂时默认回撤或者选择No 切换到项目目录:cd <your-project-name> 安装项目依赖&…

山寨APP频出?安全工程师和黑灰产在较量

在山寨这个领域&#xff0c;没有人比黑灰产更懂模仿。 据安全从业者介绍&#xff0c;一般而言&#xff0c;对于成熟的山寨开发者来说&#xff0c;几天时间内就可以做出一套前端框架。服务器、源代码、域名、服务商这些内容的创建&#xff0c;通过网上租赁的方式就可以解决。 比…

【面试题】2023前端vue面试题及答案

Vue3.0 为什么要用 proxy&#xff1f;在 Vue2 中&#xff0c; 0bject.defineProperty 会改变原始数据&#xff0c;而 Proxy 是创建对象的虚拟表示&#xff0c;并提供 set 、get 和 deleteProperty 等处理器&#xff0c;这些处理器可在访问或修改原始对象上的属性时进行拦截&…

Window问题详解(下)

建议先看一下 Window问题详解(上) 思路② 既然会超时,那该怎么办呢? 显然需要一个更快速的方法来解决这个问题! 我们先来观察一下图片: 我们发现,每一次选中的数都会增加下一个。 !!!!! 因此,我们可以根据此特性优化时间!! 第一次先求出前 k − 1 k-1 k−

hdfs file system shell的简单使用

文章目录1、背景2、hdfs file system shell命令有哪些3、确定shell操作的是哪个文件系统4、本地准备如下文件5、hdfs file system shell5.1 mkdir创建目录5.2 put上传文件5.3 ls查看目录或文件5.4 cat 查看文件内容5.5 head 查看文件前1000字节内容5.6 tail 查看文件后1000字节…

Kubernetes12:k8s集群安全机制 ***与证书生成***

Kubernetes12&#xff1a;k8s集群安全机制 1、概述 1&#xff09;访问一个k8s集群的时候&#xff0c;需要经过以下三个步骤才能完成具体操作 第一步&#xff1a;认证操作第二部&#xff1a;鉴权操作&#xff08;授权&#xff09;第三部&#xff1a;准入控制操作 2&#xff…

小白晋升大牛的13个项目

入门到放弃 “C/C真的太难学了,我准备放弃了!” 很多初学者在学完C和C的基本语法后&#xff0c;就停滞不前了&#xff0c;最终走向“从入门到放弃”。其实&#xff0c;我们初学者最需要的不是HelloWorld&#xff0c;也不是语法知识的堆砌&#xff0c;需要的只是实战项目的磨砺…

「TCG 规范解读」基础设施架构和协议 (1)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

9、STM32 SDIO FATFS(SD卡)

本篇文章使用STM32对SD卡通过SDIO配置&#xff0c;读写文件 在使用FATFS时值得注意得是若通信SDIO不启动DMA方式读写&#xff0c;容易导致其他任务中断打断读写时序&#xff0c;导致FATFS的执行出现异常&#xff0c;常见返回为FR_DISK_ERR, / (1) A hard error occurred in the…

【论文阅读】Robust Invertible Image Steganography (CVPR 2022)

作者来自北大深研院 网上已有介绍&#xff1a;https://news.pkusz.edu.cn/info/1002/6538.htm 针对传统图像隐写方法对高斯噪声、泊松噪声和有损压缩鲁棒性差的问题&#xff0c;提出了一种基于流的鲁棒可逆图像隐写框架RIIS。框架如下图 一、方法概述&#xff1a; 基于流的可…