JavaEE 【知识改变命运】03 多线程(2)

ops/2024/11/28 12:38:17/

文章目录

  • 复习
    • 1.1 进程和线程的区别
    • 1.2 线程创建的方式
    • 1.3 两者创建的区别
  • 2 多线程
    • 2.1多线程的优势-增加了运行的速度
    • 2.2Thread类及常用的方法
      • 2.2.1常用见的构造方法
      • 2.2.2获取当前类的信息
      • 2.2.3Thread 的⼏个常⻅属性
        • 1 演示后台线程
        • 2 线程是否存活
        • 3 名称
        • 4 线程中断
        • 5 等待⼀个线程 - join()
        • 6 获取当前线程引⽤
        • 7 休眠当前线程
      • 2.3.4 线程的状态

复习

1.1 进程和线程的区别

  1. 进程是申请资源的最小单位
  2. 线程是cpu调度的最小单位
  3. 创建一个进程,里面会自动生成一个主线程,进程里面包含线程
  4. 进程与进程之间互不影响,线程与线程之间可能会造成影响,一个线程的崩溃会导致整个进程的结束。
    5 线程之间共享进程的资源

1.2 线程创建的方式

继承Thread类创建方式(线程对象),重写run()方法(线程执行的任务)
实现Runnable接口创建方式,重写run()方法(线程执行的任务)
其他三种:匿名类Thread,匿名Runnable,lambda表达式Runnable接口创建方式

1.3 两者创建的区别

因为java是单继承模式,所以当一个类继承了父类,就不能再继承Thread这个类了,Runnable接口式实现,解决了单继承的这种限制
接口式创建,有高内聚低耦合的特点,把线程类和要执行的代码块分开了,后面修改代码对程序的影响较小,

2 多线程

2.1多线程的优势-增加了运行的速度

场景1:进行两次自增10_0000_0000次的自增

    private static long count=10_0000_0000;public static void main(String[] args) {//串行方式serivalThread();//并行方式parallelThread();}private static void parallelThread() {long start = System.currentTimeMillis();Thread thread01 = new Thread(()-> {int a = 0;for(int i = 0; i <count; i++) {a += i;}});Thread thread02 = new Thread(()-> {int b = 0;for(int i = 0; i <count; i++) {b += i;}});thread01.start();thread02.start();try {thread02.join();thread01.join();//一定要加这个join等待,要不然算出来的时间只是创建一个线程的时间} catch (InterruptedException e) {throw new RuntimeException(e);}long end = System.currentTimeMillis();System.out.println("并行执行时间:"+(end-start));}private static void serivalThread() {long start = System.currentTimeMillis();int a = 0;for(int i = 0; i <count; i++) {a += i;}int b = 0;for(int i = 0; i <count; i++) {b += i;}long end = System.currentTimeMillis();System.out.println("串行执行时间:"+(end-start));} }

执行结果:
在这里插入图片描述
这里注意一下:
通过多线程的方式明显提升效率,并行的耗时是串行的一半多一点时间,多一点的时间是创建线程时候消耗的时间

场景2:我们把自加次数降为50万次 执行结果;
在这里插入图片描述
注意:并不是任何时候多线程的效率比单线程的高,当任务量很少的时候,单线程的效率可能会比多线程更高
因为创建线程本身就是也有一定的系统开销,这个开销没有进程的开销大,两个线程在cpu上面调度也需要一定的时间

2.2Thread类及常用的方法

2.2.1常用见的构造方法

在这里插入图片描述
创建线程对象
这里是引用
使用Runnable创建线程对象
在这里插入图片描述
创建线程对象,并命名
在这里插入图片描述
使用Runnable创建线程对象,并命名
默认的线程名Thread-N,N>=0;

    public static void main(String[] args) {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {System.out.println("我的线程名:"+Thread.currentThread().getName());}},"Runnable线程");thread.start();} } ```执行结果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c68e1eee57af43ed948b4cc0e3d53176.png)

2.2.2获取当前类的信息

public class Mian {public static void main(String[] args) {Thread thread = new Thread(()->{//获取类名String className = Mian.class.getName();System.out.println(className);//获取线程对象Thread thread1 = Thread.currentThread();System.out.println(thread1);//获取线程名String name = thread1.getName();System.out.println(name);String mName= thread1.getStackTrace()[1].getMethodName();System.out.println(mName);},"我是一个线程");thread.start();}
}

2.2.3Thread 的⼏个常⻅属性

在这里插入图片描述

  1. id:jvm层面的,jvm默认为Thread对象生成一个编号,和PCB区分开(操作系统层面) 名字:java层面
  2. 状态:java层面定义的状态
  3. 优先级:
  4. 是否后台线程:java层面,线程分为前台线程和后台线程,通过这个表示位来区分当前前台线程还是后台线程,关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
  5. 是否存活:PCB层的,表示系统中的PCB是否销毁,与Thread对应没啥关系 ,即简单的理解,为 run ⽅法是否运⾏结束了
  6. 是否被中断:通过设置一个标志位让线程执行时候判断是否要退出
  7. 名称:名称是各种调试⼯具⽤到
  8. 注意:Thread是java中的类-----创建Thread对象----->调用strat()方法----->JVM调用系统的API生成一个PCB----PCB与Thread对象一一对应
    Thread对象与PCB所处的环境不同,所以他们的生命周期也不同。
    public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(thread.getName()+ ": ID: " + thread.getId());System.out.println(thread.getName()+ ": 名称: " + thread.getName());System.out.println(thread.getName()+ ": 状态: " + thread.getState());System.out.println(thread.getName()+ ": 优先级: " + thread.getPriority());System.out.println(thread.getName()+ ": 后台线程: " + thread.isDaemon());System.out.println(thread.getName()+ ": 活着: " + thread.isAlive());System.out.println(thread.getName()+ ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(thread.getName()+ ": 状态: " + thread.getState());} }

在这里插入图片描述

1 演示后台线程
    public static void main(String[] args) {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {while (true){System.out.println("hello myRunnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});// thread.setDaemon(true);thread.start();System.out.println("thread 是否存活"+thread.isAlive());System.out.println("main thread 已经结束");} }

在这里插入图片描述

  1. 子线程没有设置为后台线程,main线程结束,子线程不会结束 ,进程不结束在这里插入图片描述
  2. 子线程设置为后台线程,main线程结束,子线程会跟着结束,进程结束
  3. 补充:后台线程类似守护线程,main线程类似用户线程
  4. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  5. 守护线程:一般是为了工作线程服务的,当所有的用户线程结束守护线程自动结束,
  6. 常见的守护线程:垃圾回收机制
  7. 前台线程可以阻止进程结束
  8. 后台线程不能阻止进程结束
2 线程是否存活
InterruptedException {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {{System.out.println("hello myRunnable");try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});System.out.println("thread 是否存活"+thread.isAlive());//mainthread.start();//子线程System.out.println("thread 是否存活"+thread.isAlive());//mainthread.join();// 保证PCB 已经结束System.out.println("thread 是否存活"+thread.isAlive());//main,虽然PCB结束但是子线程java层面的对象还在,因为生命周期不一样//System.out.println("main thread 已经结束");} }

在这里插入图片描述

3 名称
    public static void main(String[] args) {Thread thread = new Thread(()->{//获取类名String className = Mian.class.getName();//获取方法名对象Thread thread1 = Thread.currentThread();//获取线程名String name = thread1.getName();//获取线程的方法名String mName= thread1.getStackTrace()[1].getMethodName();System.out.println("当前类名"+className+"方法名"+mName+"()"+"线程名"+name);},"我是一个线程");thread.start();thread.setName("hha")//设置线程名称,使之与参数name相同} }

在这里插入图片描述
通过类+方法+线程的名称的方法 可以明确的记录某一个线程所产生的日志

4 线程中断
  1. 通过共享的标记来进⾏沟通
    private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic 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;} } ```
  1. 调⽤ interrupt() ⽅法来通知 使⽤ Thread.interrupted() 或者
    Thread.currentThread().isInterrupted() 代替⾃定义标志位.
    private static class MyRunnable implements Runnable {@Overridepublic 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()+ ": 有内⻤,终⽌交易!");// 注意此处的 breakbreak;}}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()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");try {thread.interrupt();} catch (Exception e) {throw new RuntimeException(e);}} } ```

调用thread.interrput();方法时候
1:如果线程在运行状态,直接中断线程,不会报异常,符合程序预期
2:如果线程处于等待状态,就会报一个中断异常,要在异常处理代码块中段逻辑实现
在这里插入图片描述
当线程sleep状态时,执行中断操作,中断的是休眠状态的线程,就会抛出这个异常
在这里插入图片描述
3:PCB依然存在,任务继续执行
4. thread 收到通知的⽅式有两种:

  1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.可以选择忽 略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过 ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。
5 等待⼀个线程 - 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("王五⼯作结束了");}}
6 获取当前线程引⽤

在这里插入图片描述
Thread.currentThread()在那个线程中调用获取的就是那个线程的对象。

7 休眠当前线程

在这里插入图片描述
因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。

2.3.4 线程的状态

线程的状态继承在一个枚举中
public class Main {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}

总共有六种状态:
在这里插入图片描述
NEW:表示创建好了一个java线程对象,安排好了任务,但是没有启动,没有调用start()方法之前是不会创建PCB的,和PCB没有关系
RUNNABLE::运行+就绪状态,在执行任务时候最常见的状态之一,在系统中有对应PCB
BLOCKED:等待锁状态,阻塞的一种
WATING:没有等待时间,一直死等,直到被唤醒 TIMED_WAIRING:指定了等待时间的阻塞状态,过时不候
TERMINATED:结束,完成状态,PCB已经销毁,但是java线程对象还在

在这里插入图片描述
在这里插入图片描述
有时侯会说有七种状态,是因为又把RUNNABLE又细分为两个状态:Ready(就绪)Running
其中的yield:线程的礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功

观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换

public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());;}System.out.println(t.getName() + ": " + t.getState());;
}  ``

观察 2: 关注 WAITING 、 BLOCKED TIMED_WAITING 状态的转换

public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();} ```使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED

修改上⾯的代码, 把 t1 中的 sleep 换成 wait

public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();} ``使⽤ jconsole 可以看到 t1 的状态是 WAITING

结论:
BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知. •
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在⽆限等待唤醒
wait()是Object类里面的,Join()和sleep()是Thread类里面的。


http://www.ppmy.cn/ops/137355.html

相关文章

[代码随想录Day24打卡] 93.复原IP地址 78.子集 90.子集II

93.复原IP地址 一个合法的IP地址是什么样的&#xff1a; 有3个’.分割得到4个数&#xff0c;每个数第一个数不能是0&#xff0c;不能含有非法字符&#xff0c;不能大于255。 这个是否属于合法IP相当于一个分割问题&#xff0c;把一串字符串分割成4部分&#xff0c;分别判断每…

LeetCode 404.左叶子之和

题目&#xff1a;给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 思路&#xff1a;一个节点为「左叶子」节点&#xff0c;当且仅当它是某个节点的左子节点&#xff0c;并且它是一个叶子结点。因此我们可以考虑对整 node 时&#xff0c;如果它的左子节点是一个叶子…

网络药理学之薛定谔Schrödinge Maestro:6、分子对接(Glide、Ligand docking)和可视化

本人是win11&#xff0c;薛定谔版本是12.9。 官网&#xff1a;https://www.schrodinger.com/ 本篇文章的示例大分子蛋白PDB ID为4KNN&#xff0c;小分子配体的MOL ID为MOL004004。 本文部分图源来自知乎https://zhuanlan.zhihu.com/p/416698194&#xff0c;推荐为原作者贡献阅读…

计算机组成原理——数的定点表示和浮点表示

0.11111-2-40.9375的计算 1.1111-&#xff08;1-2-4&#xff09;-0.9375,1.1111中最前面的1是符号位 重要问题&#xff0c;因为计算机中存储的位数有限&#xff0c;所以数在计算机里是离散分布的。因为在某一区间中的数是无限的 2m-1相当于m个1表示的二进制数 -2的2m-1次方…

关于网络安全攻防知识

DNS 劫持 什么是DNS劫持&#xff1f; DNS劫持又叫域名劫持&#xff0c;&#xff08;劫持了路由器或域名服务器等&#xff09;&#xff0c;篡改了域名的解析结果&#xff0c;使得指向该域名的IP指向IP&#xff0c;你想访问正经网站结果给你跳到一个不正经的网站&#xff0c;实现…

探索Python WebSocket新境界:picows库揭秘

文章目录 探索Python WebSocket新境界&#xff1a;picows库揭秘第一部分&#xff1a;背景介绍第二部分&#xff1a;picows库概述第三部分&#xff1a;安装picows库第四部分&#xff1a;简单库函数使用方法第五部分&#xff1a;场景应用第六部分&#xff1a;常见Bug及解决方案第…

GitLab|应用部署

创建docker-compose.yaml文件 输入docker-compose配置 version: 3.8 services:gitlab:image: gitlab/gitlab-ce:15.11.2-ce.0restart: alwayscontainer_name: gitlab-ceprivileged: truehostname: 192.168.44.235environment:TZ: Asia/ShanghaiGITLAB_OMNIBUS_CONFIG: |exter…

SpringBoot集成minio,并实现文件上传

SpringBoot集成minio 什么是minioSpringBoot集成minio1、引入minio依赖2、配置Minio相关参数3、在代码里读取自定义的minio配置4、在minio配置类里,注册ConfigurationProperties实现文件上传到minio1、利用SpringMVC实现接口的异常全局处理2、返回文件路径给前端3、返回文件流…