多线程篇一

如笔者理解有误,欢迎交流指正⭐

线程

一个线程就是一个“执行流”.
每一个线程都是一个独立的执行逻辑.

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
27
28
29
// 创建一个类, 继承自 Thread .
class MyThread extends Thread {
@Override
public void run() {
// 这个方法就是线程的入口方法.
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 创建线程.
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
//thread.start(); 交替打印
thread.run();
//全部打印Welcome Thread!
while (true) {
System.out.println("Hello Main!");
Thread.sleep(1000);
}
}
}

start 和 run 都是 Thread 的成员.
run 只是描述了线程的入口(线程要做什么任务)
**start 则是真正调用了系统 API, 在系统中创建出线程, 让线程再调用 run.**(即实现并发【兵分两路 充分利用多核CPU】 Java中的“并发”多指并行+并发 区别操作系统中的宏观和微观理解)

为什么需要线程

好奇宝宝发问🤭
人多力量大!单核CPU能力有限,多核CPU可以提高算力,并发编程能充分利用多核CPU的资源.
我们是有礼貌的孩子!“IO等待”在有些场景无法避免,如何利用等待的时间做其他工作?当然少不了并发编程.
tips
多进程也能实现并发,但线程量级更轻.
1.创建线程比创建进程更快.
2.销毁线程比销毁进程更快.
3.调度线程比调度进程更快.

多线程

当进行复杂任务时,会创建多个线程进行问题处理,不同线程有不同的小任务排队执行.
操作系统中运行的任何程序都至少有一个主线程.

如何监测多线程运行情况

IDEA

debug启动有一个专门的窗口查看方法的调用栈,可以查看所有线程的信息.

下断点,右击可选择全部线程(ALL)或者指定的线程(Thread)

image-20240812112804515

debug
image-20240812113418013

Jconsole

新手推荐

启动前确保程序跑起来
Jconsole是JDK中自带的工具
Ctrl+Alt+shift+s打开project structure 查看jdk路径
image-20240812113858448

如果双击打不开 可尝试管理员方式运行
image-20240812114349198

线程和进程的区别

1.进程是包含线程的:每个进程至少有一个线程存在,即主线程.
2.进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位.

Thread.sleep()

线程休眠,方便观察,sleep是Thread的静态方法.
sleep方法可能会抛出如下异常
image-20240812115012903

此异常为受查异常,必须显示处理
必须try cacth,不能throws
上述代码重写父类的run,父类的run方法没有throws,子类也不能有(开头线程处代码 可查源码)
image-20240812115452951

sleep每秒钟打印出来的顺序也是不确定的“随机性”.
取决于操作系统对线程的调度模块,非等概率随机.

创建线程的其他写法【面试题】

非完全,后续进行补充~

继承thread 重写run(显示)

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
27
28
29
// 创建一个类, 继承自 Thread .
class MyThread extends Thread {
@Override
public void run() {
// 这个方法就是线程的入口方法.
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 创建线程.
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
//thread.start(); 交替打印
thread.run();
//全部打印Welcome Thread!
while (true) {
System.out.println("Hello Main!");
Thread.sleep(1000);
}
}
}

实现Runnable 重写run(显示)

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
27
28
29
30
31
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class Demo2 {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

while (true) {
System.out.println("Hello Main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

对比上面两种方法: 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用. 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

继承Thread 重写run 使用匿名内部类

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
27
28
public class Demo3 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};

thread.start();
while (true) {
System.out.println("Hello Main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

实现Runnable 重写run 使用匿名内部类

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
27
28
public class Demo4 {
public static void main(String[] args) {

Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();

while (true) {
System.out.println("Hello Main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用Runnable和直接继承Thread之间的主要区别

解耦合(使用反射 各个功能代码不能交叉 编写封闭的代码)

模块间的关系越密切,耦合性越强,越独立.
比如我去汉堡店打工,负责做美味蟹黄堡,客人太多,Boss又招新人负责分担相同的工作,Boss电脑被黑了,员工里只有我掌握了相关技术,那就只有我能解决问题.

做美味蟹黄堡是一个任务,我做或者新员工做本质上没有区别.可以把此任务单独提取成Runnable,可以调整执行.

反入侵只有我能来做,此任务就是和我有耦合关系.
两个任务我都可以做,那就是超级加倍!(bushi)与店面正常运转这个任务的耦合度更高.

基于lambda表达式(推荐)

Java中不允许函数独立存在(其他语言函数叫 functioin java中叫method方法)
lambda 更简化的语法表示方式 “语法糖”【例如 for each for(int x: arr) 】
相当于匿名内部类的简单写法
lambda 本质上是一个匿名函数(函数式接口 未脱离类) 主要用来实现”回调函数“的效果

1
2
3
Thread t = new Thread(() -> {

});
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
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();

while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

回调函数

是的 我们的皇帝遗诏(bushi)
非主动调用也非立即调用,在合适的时机进行执行.
通常由操作系统、库、框架、别人写的代码进行调用

Thread的常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable taeget) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable taeget,String name) 使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target) 线程可以被用来分组管理,分好的组即为线程组

创建线程的时候 可以去指定name
name不影响线程的执行 只是一个命名
方便后续调试区分

线程的入口方法执行结束 线程就被销毁
对于主线程来说 入口方法就是main方法 (例如上述t.start一瞬间就执行完毕)

Thread的几个常见属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()
getId()

线程的身份标识 标识一个进程中唯一的一个线程
(这个id式Java给这个线程分配的 不是系统api提供的线程id 更不是pcb中的id)

getPriority()

虽然提供api 可以获得/设置优先级 但很难被察觉
优先级影响到的是微观上进行的调度

isDaemon()
守护线程(后台线程)

不结束不影响整个进程的结束

前台线程

一个Java进程中 若前台线程未执行结束 此时整个进程是一定不会结束的

1
2
3
4
//设置t为后台线程 一定在start之前
t.setDaemon(true);

t.start();

默认情况下一个线程是前台线程,未处理的线程默认为前台线程
改成后台线程之后 主进程飞快的结束 此时没有其他前台线程 进程结束 t来不及执行就完了

isAlive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo7 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
});

System.out.println(t.isAlive());
t.start();
System.out.println(t.isAlive());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.isAlive());
}
}

判定内核中的线程是不是被销毁了(回调方法执行完毕 线程就结束了)

Thread对象的生命周期 比系统内核中的线程更长一些
Thread对象还在 内核中的线程已经被销毁
image-20240812144335773

线程并发执行,并发顺序由系统的调度器决定,不确定.(极端情况可能主线程卡新线程先打印)

未完待续❀❀❀