• 总结 Thread 类的基本用法



    🎑线程是什么?

    也把线程称为轻量级进程

    如果把进程想象成工厂,线程就是工厂中的"流水线"

    使用多线程:

    1. 能够充分利用多核CPU,能够提高效率~
    2. 只是创建第一个线程的时候,需要申请资源,后续再创建新的线程,都是共用同一份资源
      节省了申请资源的开销
      销毁线程的时候,也只是销毁到最后一个的时候,才真正的释放资源
      前面的线程销毁,都不必真正释放资源

    操作系统内核,是通过PCB来描述进程的~~
    更准确的说法 : 是一组PCB来描述一个进程,每个PCB对应一个线程

    一个进程至少有一个线程,也可以有多个~

    这一组PCB上的内存指针,和文件描述符表,其实是同一份~
    而状态,上下文,优先级,几张信息,则是每个PCB(每个线程)自己有一份

    进程是 资源分配的基本单位
    线程是 调度执行 的基本单位

    ❗ ❗面频面试题(面试必考):
    谈谈进程和线程之间的区别?

    1. 进程包含线程
    2. 线程比进程更轻量,创建更快,销毁更快
    3. 同一个进程的多个线程之间共用同一份内存/文件资源,而进程和进程之间则是独立的内存/文件资源
    4. 进程是资源分配的基本单位,线程是调度执行的基本单位
    5. 线程之间可能会相互影响到.
      如果两个线程同时修改同一个变量,容易产生"线程不安全"的问题
    6. 如果某个线程出现异常,并且异常没有处理好的话,整个进程都会随之崩溃!!这个时候后续其他线程自然难以进行运行

    线程数目不是越多越好,CPU核心数是有限的,当线程数目达到一定程度的时候,CPU核心数已经被吃满了!!此时继续增加线程,也无法再提升效率了~反而会因为线程太多,线程调度开销太大,影响了效率!

    ❓如果CPU是6核心,此时6个线程就是吃满了吗?

    不一定,一个线程可能在占用CPU,也可能在等待~
    另外,现代的CPU都有"超线程技术",一个核心可以并行跑两个线程(比如12核24线程)

    在这里插入图片描述

    🎎一、线程创建

    🧨1.继承Thread类,重写Thread里的run方法

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Thread相当于操作系统中的线程进行的封装
    runThread父类已经有的方法了
    但是这里要重写一下~
    run里面的逻辑,就是这个线程要执行的工作!!
    创建子类,并且重写run方法,相当于"安排任务"

    创建一个MyThread实例,创建实例并不会在系统中真的创建一个线程!!
    调用start方法的时候,才是真正创建出一个新的线程~~

    Thread myThread = new MyThread();
            myThread.start();
    
    • 1
    • 2

    新的线程就会执行run里面的逻辑~直到run里的代码执行完,新的线程就运行结束了

    运行了一次Java程序,就是启动了一个进程~
    一个进程里至少会有一个线程~默认的线程正是main方法所在的线程(也叫做主线程)

    main主线程,和MyThread创建出来的新线程,是一个并发执行(宏观的,其实是并发+并行)的关系!

    并发执行的意思是两边同时执行,各自执行各自的!

    另外启动一个线程,来执行Threadrun方法!!
    新的线程是一个单独的执行流,和现有进程的执行流不相关

    多个线程调度的时候,会发生抢占式执行!

    “抢占式执行” :

    操作系统在调动线程的时候,是一个"随机"的进程!!
    执行sleep,线程进入阻塞
    sleep时间到,线程恢复就绪状态,参与线程调度

    当两个线程都参与调度的时候,谁先谁后,不确定!

    是多线程编程的"万恶之源,罪魁祸首"
    就给多线程程序的执行,带来了非常多的变数
    写代码的时候,可能某些调度顺序下,代码没问题,某些调度顺序下,代码就出bug!!
    编写多线程代码,就需要考虑所有可能的调度情况,保证每种情况下,都不出问题…

    耦合性比较高,假设未来要对这个代码进行调整(不用多线程了,用其他方式),代码改动就比较大

    在这里插入图片描述

    🧨2.通过重写Runnable接口,重写run

    class MyRunnable implements Runnable {
    
        @Override
        public void run() {
            System.out.println(2);
        }
    }
    public class TestDemo {
        public static void main(String[] args) {
            Runnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    把线程要干的活和线程本身分开了~
    使用Runnable来专门表示"线程要完成的工作"
    把任务提取出来,目的是为了解耦合,改成其他方式只需要把Runnable传给其他的实体就行~
    如果想使多个线程都干一样的活,也更适合使用Runnable
    在这里插入图片描述

    🧨3.使用匿名内部类,来创建Thread的子类

    匿名内部类的实例,作为构造方法的参数!!

    Thread myThread0 = new MyThread() {
                @Override
                public void run() {
                    System.out.println(3);
                }
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建Thread的子类,同时实例化出一个对象!

    在这里插入图片描述

    🧨4.使用匿名内部类,实现Runnable接口的方式

    在构造方法的参数里面就地创建一个匿名内部类
    匿名内部类的实例,作为构造方法的参数!

     Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(4);
                }
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    🧨5.lambda表达式

    Thread thread2 = new Thread(()->{
                while (true) {
                    System.out.println(5);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    复习lambda表达式:

    本质上是一个"匿名函数"
    ()函数的形参 {}函数体
    -> 特殊语法,表示它是一个lambda

    ❗ 线程创建不止这五种!

    在这里插入图片描述

    🎎二、线程中断

    run方法执行完了,线程就结束了
    ❓有没有方法让线程提前一点结束呢?
    答案是通过线程中断(本质仍然是让run方法尽快结束,而不是run执行一半,强制结束)

    这就取决于run里面具体是如何实现的了

    1. 直接自己定义一个标志位,作为线程是否结束的标记~
    public class TestDemo {
        private static boolean isQuit = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(()-> {
                while (!isQuit) {
                    System.out.println("isQuit为假");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
            });
    
            thread.start();
    
            Thread.sleep(1000);
            System.out.println("中断线程");
            isQuit = true;
            System.out.println("isQuit 为真");
    
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 使用标准库里自带的标志位
    Thread.currentThread().isInterrupted();
    
    • 1

    currentThread()Thread类的静态方法, 通过这个方法,就可以拿到当前线程的实例(拿到当前线程对应的Thread对象)


    isInterrupted()判定标志位:
    在主线程中,通过thread.Interrupt()来中断这个线程,设置标志位为true!!
    interrupt方法的行为有两种情况:

    1. thread线程在运行状态时,isInterrupted()会设置 Thread.currentThread().isInterrupted();的标志位为true
    2. thread线程在阻塞状态(sleep)会抛出异常,会清除这个标志位(设置了又清除了),并触发一个 InterruptedException异常,这个异常会把sleep提前唤醒!
      但是由于代码中只是打印了日志,而没有结束循环,因此线程还是在继续执行中!
      在这里插入图片描述

    在这里插入图片描述

    在java中,中断线程并非是强制的!!
    是由线程自身的代码来进行判定处理的!!
    线程自身能怎么处理呢?

    1. 立即结束线程
    2. 不理会
    3. 稍后理会

    在这里插入图片描述

    🎎 三、线程等待

    join()阻塞等待,线程的结束

    class MyRunnable implements Runnable {
        public static final long COUNT = 10_0000_0000L;
        @Override
        public void run() {
            int x = 0;
            for (int i = 0; i < COUNT; i++) {
                x++;
            }
        }
    }
    public static void fun2() {
            Runnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            Thread thread1 = new Thread(myRunnable);
            long beg = System.currentTimeMillis();
    
            thread.start();
            thread1.start();
    
            thread.join();
            thread1.join();
            long end = System.currentTimeMillis();
            System.out.println(end-beg + "ms");
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    线程之间的调度顺序,是不确定的!!
    可以通过一些特殊操作,来对线程的执行顺序,做出干预

    其中join是一个方法,控制线程之间的结束顺序!!

    💥上面的代码如果不加thread.join(); thread1.join();是有问题的,因为’main’线程和’thread,thread1是并发执行的,thread,thread1还没有执行完的时候就计算时间了…

    要让main线程等待threadthread1执行完了,才能停止计时~

    main中调用thread.join 效果就是让main线程阻塞,一直到thread执行完run,main才继续执行
    main中调用thread1.join 效果就是让main线程阻塞,一直到thread1执行完run,main才继续执行
    (执行join的线程(比如main线程)就阻塞,阻塞到调用join的线程执行完毕之后才继续执行)

    threadthread1是并发执行(宏观上的同时),而不是先执行完thread,才执行thread1

    此处写两个join就是两个线程都执行完,才继续执行计时操作!!

    Java中的多线程方法,只要阻塞就都可能触发InterruptedException异常

    如果是调用join之前,线程已经结束了,此时join还需要阻塞等待吗?
    🎫答案:不需要
    在这里插入图片描述

    🎎四、线程休眠

    sleep();
    
    • 1

    指定休眠的时间~~让线程休息一会儿(阻塞一会儿)

    操作系统管理这些线程的PCB时,是有多个链表的~~

    在这里插入图片描述

    当1号线程,sleep的时间到了!!就会被移动回之前的就绪队列~~

    挪回了就绪队列,不代表立刻就能上CPU上执行,还得看系统什么时候调度到这个线程!!

    sleep(1000),不一定就真的是休眠了1000,一般是要略多于1000,具体多了多少,得看调度的时间开销…(可能快,比如<1ms,也可能慢,比如10ms左右)

    通常的操作系统,调度开销都是不可预期的,可能快可能慢

    有些场景,不希望调度开销太大,速度太慢!!(比如航空航天等精密的工作)
    因此为了解决这个问题,可以使用"实时操作系统"
    特点就是任务调度的开销,是可预期的(做到实时,也要付出代价,所以普通工作不用)
    在这里插入图片描述

    🎎五、获取线程实例

    public static Thread currentThread(); 
    Thread.currentThread();
    
    • 1
    • 2

    获取到当前这个线程对应的Thread对象的引用

    哪个线程里面调用,得到的就是哪个线程的引用

    总结

    在这里插入图片描述

    你可以叫我哒哒呀
    非常欢迎以及感谢友友们的指出问题和支持!
    本篇到此结束
    “莫愁千里路,自有到来风。”
    我们顶峰相见!
  • 相关阅读:
    毕业设计:基于Springboot+Vue+ElementUI实现疫情社区管理系统
    使用Git把项目上传到Gitee的详细步骤
    数据库设计
    Excel数据丢失怎么找回?详细恢复教程分享!
    深度学习目标检测YOLO系列(v1-v3+tinyv3)
    SDRAM 控制器(三)——自动刷新模块
    LeetCode二叉树系列——236.二叉树的最近公共祖先
    [含毕业设计论文+PPT+源码等]springboot巧匠家装小程序+Java后台管理系统|前后分离VUE
    少走点弯路:Wiki.js 通过 Generic OAuth2 进行身份验证
    扬帆际海—移动端流量对跨境电商有多重要?
  • 原文地址:https://blog.csdn.net/m0_58437435/article/details/126319775