Java-多线程
Xplorist Lv6

Java-多线程

reference-site-list

steps

  • 个人对多线程的一些认识:工作这么久了,现在对多线程的认识最深刻的在于异步,对于一个耗时请求避免出现客户端超时,直接返回客户端请求成功,通过生成一个子线程,让子线程去执行耗时操作,当后端执行完成后,再用websocket推送给前端执行结果。

同步和异步

  • 同步:多个操作,按照顺序执行,只有当上一个操作执行完成后才能够执行下一个,单一时间内只能执行一个操作。关键在于,上一个动作未完成,下一个动作必须等待。

  • 异步:多个操作,一个操作执行的过程中,另外的动作也可以执行,单一时间内可以执行多个操作。关键在于,两个动作可以同时执行,不需要等待。

举例: 就拿做饭来说,做饭分为:煮饭和炒菜连个操作。同步就是:只有等饭煮熟了,才开始炒菜。异步就是:把米淘好放进锅里煮,不等饭煮熟,煮饭的同时就开始炒菜。

总结:同步异步的关键在于需不需要等待。

  • 同步是当前行为的开始依赖于上一个行为的结束,让我联想到了电路里面的时序逻辑电路,当前的结果不仅取决于当前的输入,还依赖于上一个状态,有点跑题了。

个人对同步异步了解最深刻的场景是前端请求,同步请求是当返回结果后才开始执行后面的代码,而异步请求就是不用等待请求结果返回,就开始往下面执行代码。有些时候前端必须要等待后端请求的结果才能够往下执行,就需要把异步请求变成同步请求,之前js里面没有await,AngularJs里面尝试过很多将异步变成同步请求的方式都失败了,所以印象非常深刻。

多线程是什么

  • 一个程序中对多个任务进行CPU时间分配,每一个任务执行一小段时间,不停切换,实现多个任务同时进行,但是单核CPU在一个时间点只有一个任务在执行,一个任务做一点,然后就切到另外一个任务做一点,反复切换,就是多个任务都在进行中了。
  • 可以这样理解,例如:做作业,传统的单线程,就是语文做完了,再去做数学,数学做完了,再去做英语。多线程的方式,就是语文做完一个题,就去做数学的一个题,数学这个题做完了再去做英语的一个题,这样就是三门课程的作业都在进行中,但是一个时间里只能做一门课程的一个题。
  • 如果是多核的情况下,就相当于有多个人在做作业,是可以实现同一时间内做两门功课的。比如小明的作业太多了,请来了他的哥哥小黑帮他做,小明在做语文题的同时,他的哥哥在做数学题。
  • 理解多线程的核心在于切换二字。

为什么要有多线程

  • 为了实现异步,比如有些耗时的任务,会让CPU停下来等待该任务执行完成后再执行其他任务,浪费了时间资源,而多线程实现了多个任务切换运行,不必等待耗时任务执行完成,其他任务也能得到运行,节省了时间。
  • 打个比方,就像是做饭,单线程就是等饭煮熟了再去做菜,而多线程是,米下锅后,就开始做菜,等饭煮熟的时间被用来做菜,节约了时间。

多线程怎么用

Java的创建多线程的方式有4种,

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口 (从JDK1.5开始增加的, 有返回值)
  • 通过线程池创建

继承Thread类

  • ExtendThread.java
1
2
3
4
5
6
7
8
9
10
package tech.xplorist.y2021.m11.d21;

public class ExtendThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("thread name: " + this.getName() + "->, i = " + i);
}
}
}

实现Runnable接口

  • RunnableThread.java
1
2
3
4
5
6
7
8
9
10
package tech.xplorist.y2021.m11.d21;

public class RunnableThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
MultiThreadTest.printThreadName(i);
}
}
}

实现Callable接口

  • CallableThread.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package tech.xplorist.y2021.m11.d21;

import java.util.concurrent.Callable;

public class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer num = 0;
for (int i = 0; i < 10; i++) {
MultiThreadTest.printThreadName(i);
num += i;
}
return num;
}
}

通过线程池创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 测试线程池
public static void testExecutor() {
ExecutorService es = Executors.newFixedThreadPool(3);
es.execute(new RunnableThread());
es.submit(new RunnableThread());
Callable<Integer> ctc = new CallableThread();
FutureTask<Integer> callableTask = new FutureTask<>(ctc);
es.submit(callableTask);
for (int i = 0; i < 10; i++) {
printThreadName(i);
}
try {
Integer result = callableTask.get();
System.out.println("task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
es.shutdown();
}

完整测试类

  • MultiThreadTest.java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package tech.xplorist.y2021.m11.d21;

import java.util.concurrent.*;

public class MultiThreadTest {
public static void main(String[] args) {
testExtend();
testRunnable();
testCallable();
testExecutor();
}

// 打印线程名
public static void printThreadName(int num) {
System.out.println("thread name: " + Thread.currentThread().getName() + " -> i = " + num);
}

// 测试继承式线程
public static void testExtend() {
ExtendThread et = new ExtendThread();
et.start();
for (int i = 0; i < 10; i++) {
printThreadName(i);
}
}

// 测试runnable式线程
public static void testRunnable() {
RunnableThread rt = new RunnableThread();
Thread thread = new Thread(rt);
thread.start();
for (int i = 0; i < 10; i++) {
printThreadName(i);
}
}

// 测试callable式线程
public static void testCallable() {
Callable<Integer> ctc = new CallableThread();
FutureTask<Integer> task = new FutureTask<>(ctc);
Thread thread = new Thread(task);
thread.start();
for (int i = 0; i < 10; i++) {
printThreadName(i);
}
try {
Integer result = task.get();
System.out.println("task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}

// 测试线程池
public static void testExecutor() {
ExecutorService es = Executors.newFixedThreadPool(3);
es.execute(new RunnableThread());
es.submit(new RunnableThread());
Callable<Integer> ctc = new CallableThread();
FutureTask<Integer> callableTask = new FutureTask<>(ctc);
es.submit(callableTask);
for (int i = 0; i < 10; i++) {
printThreadName(i);
}
try {
Integer result = callableTask.get();
System.out.println("task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
es.shutdown();
}
}

线程锁与线程同步

  • 多个线程共有的数据,为了保证数据的正确,只允许单位时间内只有一个线程去操作该数据,当这个线程对数据的操作完成退出后,才允许后面的线程进入。这种给资源只限定一个线程操作的方式,就是给资源加线程锁。多个线程要操作某个资源,但是不能同时对该资源进行操作,只能等上一个线程操作完成后才能进行操作的过程,叫做线程同步。
  • 同步的核心还是等待;锁的核心是排他性,即互斥,只允许有一个线程拥有,也叫独占。
  • 举个例子:就好比上厕所,只有一个厕所坑位,每个人使用都会把厕所锁住,不让其他人使用。
  • 每个人锁住厕所不让其他人使用,这就相当于给资源加上线程锁,不允许其他线程使用。
  • 每个人都必须等上一个人上完厕所,然后才能进入厕所的过程,就叫做同步。
  • 这是一个有味道的例子(手动狗头)。
 评论