Java-多线程
reference-site-list
steps
- 个人对多线程的一些认识:工作这么久了,现在对多线程的认识最深刻的在于异步,对于一个耗时请求避免出现客户端超时,直接返回客户端请求成功,通过生成一个子线程,让子线程去执行耗时操作,当后端执行完成后,再用websocket推送给前端执行结果。
同步和异步
举例: 就拿做饭来说,做饭分为:煮饭和炒菜连个操作。同步就是:只有等饭煮熟了,才开始炒菜。异步就是:把米淘好放进锅里煮,不等饭煮熟,煮饭的同时就开始炒菜。
总结:同步异步的关键在于需不需要等待。
- 同步是当前行为的开始依赖于上一个行为的结束,让我联想到了电路里面的时序逻辑电路,当前的结果不仅取决于当前的输入,还依赖于上一个状态,有点跑题了。
个人对同步异步了解最深刻的场景是前端请求,同步请求是当返回结果后才开始执行后面的代码,而异步请求就是不用等待请求结果返回,就开始往下面执行代码。有些时候前端必须要等待后端请求的结果才能够往下执行,就需要把异步请求变成同步请求,之前js里面没有await,AngularJs里面尝试过很多将异步变成同步请求的方式都失败了,所以印象非常深刻。
多线程是什么
- 一个程序中对多个任务进行CPU时间分配,每一个任务执行一小段时间,不停切换,实现多个任务同时进行,但是单核CPU在一个时间点只有一个任务在执行,一个任务做一点,然后就切到另外一个任务做一点,反复切换,就是多个任务都在进行中了。
- 可以这样理解,例如:做作业,传统的单线程,就是语文做完了,再去做数学,数学做完了,再去做英语。多线程的方式,就是语文做完一个题,就去做数学的一个题,数学这个题做完了再去做英语的一个题,这样就是三门课程的作业都在进行中,但是一个时间里只能做一门课程的一个题。
- 如果是多核的情况下,就相当于有多个人在做作业,是可以实现同一时间内做两门功课的。比如小明的作业太多了,请来了他的哥哥小黑帮他做,小明在做语文题的同时,他的哥哥在做数学题。
- 理解多线程的核心在于切换二字。
为什么要有多线程
- 为了实现异步,比如有些耗时的任务,会让CPU停下来等待该任务执行完成后再执行其他任务,浪费了时间资源,而多线程实现了多个任务切换运行,不必等待耗时任务执行完成,其他任务也能得到运行,节省了时间。
- 打个比方,就像是做饭,单线程就是等饭煮熟了再去做菜,而多线程是,米下锅后,就开始做菜,等饭煮熟的时间被用来做菜,节约了时间。
多线程怎么用
Java的创建多线程的方式有4种,
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口 (从JDK1.5开始增加的, 有返回值)
- 通过线程池创建
继承Thread类
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接口
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接口
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(); }
|
完整测试类
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); } }
public static void testRunnable() { RunnableThread rt = new RunnableThread(); Thread thread = new Thread(rt); thread.start(); for (int i = 0; i < 10; i++) { printThreadName(i); } }
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(); } }
|
线程锁与线程同步
- 多个线程共有的数据,为了保证数据的正确,只允许单位时间内只有一个线程去操作该数据,当这个线程对数据的操作完成退出后,才允许后面的线程进入。这种给资源只限定一个线程操作的方式,就是给资源加线程锁。多个线程要操作某个资源,但是不能同时对该资源进行操作,只能等上一个线程操作完成后才能进行操作的过程,叫做线程同步。
- 同步的核心还是等待;锁的核心是排他性,即互斥,只允许有一个线程拥有,也叫独占。
- 举个例子:就好比上厕所,只有一个厕所坑位,每个人使用都会把厕所锁住,不让其他人使用。
- 每个人锁住厕所不让其他人使用,这就相当于给资源加上线程锁,不允许其他线程使用。
- 每个人都必须等上一个人上完厕所,然后才能进入厕所的过程,就叫做同步。
- 这是一个有味道的例子(手动狗头)。