总结 Thread 类的基本用法

发布于:2022-12-22 ⋅ 阅读:(451) ⋅ 点赞:(0)


🎑线程是什么?

也把线程称为轻量级进程

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

使用多线程:

  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);
    }
}

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

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

Thread myThread = new MyThread();
        myThread.start();

新的线程就会执行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();
    }
}

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

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

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

Thread myThread0 = new MyThread() {
            @Override
            public void run() {
                System.out.println(3);
            }
        };

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

在这里插入图片描述

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

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

 Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(4);
            }
        });

在这里插入图片描述

🧨5.lambda表达式

Thread thread2 = new Thread(()->{
            while (true) {
                System.out.println(5);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

复习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. 使用标准库里自带的标志位
Thread.currentThread().isInterrupted();

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");
       }

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

其中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();

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

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

在这里插入图片描述

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

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

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

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

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

🎎五、获取线程实例

public static Thread currentThread(); 
Thread.currentThread();

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

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

总结

在这里插入图片描述

你可以叫我哒哒呀
非常欢迎以及感谢友友们的指出问题和支持!
本篇到此结束
“莫愁千里路,自有到来风。”
我们顶峰相见!

网站公告

今日签到

点亮在社区的每一天
去签到