Java 多线程的一次整理
一天没有出过家门,实属无聊,没事瞎写写
1. 基本概念
1.1 多进程和多线程的概念
程序是由指令和数据组成,指令要运行,数据要加载,指令被 CPU 加载运行,数据被加载到内存,指令运行时可由 CPU 调度硬盘、网络等设备。一个线程就是一个指令,CPU 调度的最小单位,一个进程就是一系列的指令流,由 CPU 一条一条执行
-
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。一系列指令
-
线程是进程中的实际运行单位,是独立运行与进程之中的子任务。一条单独的指令
1.2 并行与并发
并发和并行都是同时处理多路请求,目的是最大化 CPU 的利用率。并行是指两个或者多个事件在同一时刻发生,并发是指多个事件在同一事件间隔内发生
-
并发是指单核 CPU 运行多线程时,时间片进行很快的切换,线程轮流执行 CPU
-
并行是指多核 CPU 运行多线程时,真正的在同一时刻运行
1.3 计算机存储体系
在很早之前,CPU 的频率与内存的频率在一个层面上,上世纪 90 年代,CPU 的频率大大提升,但内存的频率没有得到提升,导致 CPU 的运行速度比内存读写速度快很多,使 CPU 花费很长的时间等待数据的到来或把数据写入到内存中。为了解决 CPU 运算速度与内存读写速度不匹配的矛盾,就出现了 CPU 缓存,CPU 缓存分为三个级别,分别是 L1、L2、L3,级别越小越接近 CPU,速度也越来越快,容量也越来越小
多核 CPU 的情况下有多个一级缓存,如何保证缓存内部数据一致性,不让系统数据混乱,解决方案就是缓存一致性协议(Modified Exclusive Shared Or Invalid,MESI)或者锁住总线,其中锁住总线,效率非常低下CPU 串行,所以实际使用 MESI。MESI 通过四种状态来进行标记
状态 | 描述 | 监听任务 | 状态转换 |
---|---|---|---|
M 修改(Modified) | 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 | 缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。 | 当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。 |
E 独享、互斥(Exclusive) | 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。 | 缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。 | 当CPU修改该缓存行中内容时,该状态可以变成Modified状态 |
S 共享(Shared) | 该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。 | 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。 | 当有一个CPU修改该缓存行时,其它CPU中该缓存行可以被作废(变成无效状态 Invalid)。 |
I 无效(Invalid) | 该Cache line无效。 | 无 | 无 |
对于 M 和 E 状态而言总是精确的,他们在和该缓存行的真正状态是一致的,而 S 状态可能是非一致的
1.4 线程的状态
sleep、yield 和 join 区别:
-
sleep 执行后线程进入阻塞状态,当前线程休眠一段时间
-
yield 执行后线程进入就绪状态,使当前线程和所有等待的线程一起进行竞争 CPU 资源
-
join执行线程进入阻塞状态,t.join 表示阻塞调用此方法的线程,直到线程 t 完成,方可继续执行。底层实际调用 wait 方法
-
新建状态(New):线程对象被创建后,就进入了新建状态。例如:Thread thread = new Thread()
-
就绪状态(Runnable):也被称为”可执行状态”。线程对象呗创建后,其它线程调用了该对象的 start() 方法,从而就启动该线程。例如T.stat(),处于就绪状态的线程,随时可能被CPU调度执行
-
运行状态(Running):线程获取 CPU 权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态
-
阻塞状态(Blocked):阻塞状态是线程放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
-
等待阻塞:通过调用线程的wait() 方法,让线程等待某工作的完成
-
同步阻塞:线程在获取 synchronized 同步锁失败,它会进入同步阻塞状态
-
其它阻塞:通过调用线程的 sleep() 或 join() 或发出 I/O 请求时,线程会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入到就绪状态
-
-
死亡状态(Dead):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期
2.多线程的实现方式
2.1继承 Thread 类创建线程
Thead 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例
1 /** 2 * @description: 多线程实现方法1:集成Thread类 3 * @author: DZ 4 **/ 5 @Slf4j 6 public class MyThread1 extends Thread { 7 @Override 8 public void run() { 9 log.info("MyThread1"); 10 log.info("MyThread2"); 11 } 12 13 public static void main(String[] args) { 14 MyThread1 t1 = new MyThread1(); 15 MyThread1 t2 = new MyThread1(); 16 t1.start(); 17 t2.start(); 18 } 19 }