一文帮你深度剖析多线程相关知识(基础篇上)

引言

大家好,我是青花瓷,前面一篇博客给大家深度剖析了进程和线程的概念以及进程和线程的区别,今天给大家深度剖析多线程的相关知识,如果本篇博客对广大友友有帮助,三连支持一下哟???

一文概括进程和线程的区别和联系博客地址:

https://editor.csdn.net/md/?articleId=123648471

在这里插入图片描述

?一.Thread类的基本用法

?‍?1.创建子类,继承自Thread,并重写run方法

这个方法是,Thread 类创建线程中最简单的方法

在这里插入图片描述

注意:run方法中的逻辑,是在新创建出来的线程中,被执行的代码,并不是我一定义这个类,一写run方法,线程就创建出来了,这里相当于我把活安排出来了,但是家人们还没开始干呢

在调用 start 之前,系统中是没有创建出线程的,这里调用了 start 方法之后,才是真正的在系统中创建了线程,才是真正开始执行上面的run操作
在这里插入图片描述

?‍?sleep操作

线程间是并发执行的,如果在一个循环中不在任何限制,这个循环转的速度非常快,导致打印的东西太多了,根本看不过来,就可以加上一个 sleep 操作,来强制让这个线程休眠一段时间.(这个休眠操作,就是强制的让线程进入阻塞状态)

在这里插入图片描述

?‍?如何并发执行

在一个进程中,至少会有一个线程,在一个java进程中,也是至少会有一个调用 main 方法的线程(这个线程不是你手动搞出来的),自己创建的 t 线程 和 自动创建的 main 线程,就是并发执行关系(此处的并发 = 并行 + 并发)

在这里插入图片描述
输出执行结果:
在这里插入图片描述

通过输出执行结果可以看出:现在两个线程,都是打印一条,就休眠个1s,当 1s 时间到了之后,系统先唤醒谁呢?看起来这个顺序不是完全正确的(随机的)

每一轮,1s时间到了之后,到底是先唤醒 main 还是 thread,这是不确定的(随机的),对于操作系统来说,内部对于线程之间的调度顺序,在宏观上可以认为是随机的(抢占式执行)

?‍?2.创建一个类实现Runnable接口.再创建Runnable实例传给Thread实例

创建一个类,实现Runnable接口,再创建 Runnable实例传给 Thread 实例

在这里插入图片描述

?‍?3.使用匿名内部类

在这里插入图片描述
此处任然是调用 start 来开启线程

?‍?4.匿名内部类 new 的 Runnable

在这里插入图片描述

?‍?5.使用lambda表达式

相当于是第四种方法的衍生 lambda代替 Runnable

在这里插入图片描述

以上五种写法都很常见,大家都一定要熟悉!

?二.多线程提高任务完成的效率题例

有两个整数变量,分别要对这俩变量自增10亿次,分别使用一个线程和两个线程

是用一个线程
在这里插入图片描述
用两个线程
在这里插入图片描述
主函数
在这里插入图片描述
具体代码:

public class Demo7 {
    private static final long count = 10_0000_0000;
    public static void serial() {
        //记录程序执行时间
        long beg = System.currentTimeMillis();//记录开始时间戳
        long a = 0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b = 0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end = System.currentTimeMillis();//记录结束时间戳
        System.out.println("消耗时间: " + (end - beg) + "ms");
    }
    // 两个线程执行
    public static void concurrency() throws InterruptedException {
        long beg = System.currentTimeMillis();//开始时间
        Thread t1 = new Thread(() -> {
            long a = 0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
           long b = 0;
            for (int i = 0; i < count; i++) {
                b++;
            }
        });
        t2.start();
        // 此处不能直接就这么记录结束时间,别忘了,现在这个求时间戳的代码是在main线程中
        // main 和 ti t2 之间并发执行的关系,此处ti 和 t2还没执行完呢,这里就开始记录时间了,这显然不准确
        // 正确的做法应该是 main线程等待 t1 和 t2 跑完了,再来记录时间
        // join 效果就是等待线程结束,t1.join就是让 main线程等待t1结束 同理
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();//结束时间
        System.out.println("消耗时间: " + (end - beg) + "ms");
    }
    public static void main(String[] args) {
        serial();//一个线程.串行执行 大概 600多ms
        try {
            concurrency();//两个线程并发 大概400ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:
在这里插入图片描述

?三.多线程一定能提高效率吗?

注意:多线程不是万能良药,不是你上了多线程,速度一定能够提高!还得看具体的场景,多线程特别适合于那种CPU密集型的程序,程序需要大量的计算,使用多线程就可以更充分的利用CPU的多核资源

?四.Thread类的其他的属性和方法

Thread类的常用属性及说明如下表所示

在这里插入图片描述
具体说明以下属性:

Thread(String name):

在这里插入图片描述

是否后台运行 isDaemon():

在这里插入图片描述

是否存活 isAlive():

在这里插入图片描述

Thread类的常用方法及说明如下表所示

在这里插入图片描述

?五.Thread中的一些重要方法

start: 决定了系统是不是真的创建出了线程

start 和 run 的区别

在这里插入图片描述
run 单纯的只是一个普通的方法,描述任务的内容,start 则是一个特殊的方法,内部会在系统中创建线程

?六.中断线程

中断线程:让一个线程停下来.线程停下来的关键是要让线程对应的 run 方法执行(还有一个特殊的是main 这个线程,对于 main 来说,得是main 方法执行完,线程就完了)

1.可以手动的设置一个标志位(自己创建的变量,boolean)来控制线程是否要执行结束

在这里插入图片描述
但是这个做法并不严谨,更好的做法是:

2.使用Thread中内置的一个标志位,来进行判定,可以通过:
Thread.interrupted() 这个是静态方法
Thread.currentThread().isInterrupted() 这个是实例方法

在这里插入图片描述
补充:

在这里插入图片描述

?七.线程等待

多个线程之间,调度顺序是不确定的,线程之间的执行是按照调度器来安排的,这个过程可以视为是"无序,随机",这样不太好,有些时候,我们需要能够控制线程之间的顺序,线程等待就是其中一种,控制线程执行顺序的手段,此处的等待,主要是控制线程结束的先后顺序

两行板书帮助大家更好的理解 线程的等待:

在这里插入图片描述
在这里插入图片描述

?八.线程休眠

在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>