我们探讨有关 多线程并发-原子-锁机制原理-死锁 的问题。
1.我们先了解一个概念,什么是程序?
程序分为两个状态,
非运行状态下: 代码文本 + 资源数据
2.进程与线程的区别?
3.线程在CPU中运行,CPU的概念?
CPU(中央处理器):是计算机的核心部件,负责处理计算机中的数据和指令。CPU也被称为处理器或微处理器。
核心数量 + 线程数量
4核心8线程:使用超线程技术,把一个物理核心,模拟成两个逻辑核心。每个物理核心可以同时执行2个线程,因此总共有8个线程可以同时执行。
CPU线程数量:一般是4-32 ,64以上一般用于服务器大计算的功能。
操作系统实际运行线程数量:2000-10000
此处为8核16线程,正在运行线程数量3880左右。CPU使用16个逻辑处理器,处理3880个线程(每个逻辑处理器同时智能处理一个线程)
CPU的主要功能:
- 执行指令:CPU从内存中读取指令,并根据指令对数据进行处理。
- 进行运算:CPU可以进行各种算术运算和逻辑运算,如加、减、乘、除、与、或、非等。
- 控制设备:CPU可以控制计算机中的各种设备,如内存、硬盘、显卡、声卡等。
CPU由以下几个部分组成:
- 运算器:执行算术运算和逻辑运算。
- 控制器:控制CPU的各个部件,并协调CPU与其他设备之间的通信。
- 寄存器:是CPU内部的临时存储器,用于存储指令、数据和地址等信息。
- 总线:CPU与其他设备之间的数据通路。
CPU的工作原理如下:
- CPU从内存中读取指令。
- CPU将指令解码,并确定指令的操作码和操作数。
- CPU根据指令的操作码和操作数执行相应的操作。
- CPU将结果存储在寄存器或内存中。
CPU通过调度器来调度线程。通过下面常见的调度算法来执行:
- 时间片轮转调度算法:将CPU时间划分为一个个时间片,每个线程轮流执行一个时间片。当一个线程的时间片用完时,它会被挂起,下一个线程开始执行。
- 优先级调度算法:优先级高的线程会被优先执行。
- 多级反馈队列调度算法:将线程分为多个队列,每个队列都有不同的优先级。当一个线程在高优先级队列中执行完后,它会被移动到低优先级队列中。
在多线程的CPU上面写多线程程序才能够真正提高效率。
4.八大原子操作
原子性:CPU能够保证在一条指令执行期间不会被中断,聪儿保证指令的完整性和数据的正确性。
八大原子操作是指在多处理器系统中,保证原子性的一组基本操作。分别是:
- 读-改-写 (Read-Modify-Write):在一个操作中读取一个值,修改它,然后将其写回。
- 加载链接 (Load-Link):将一个值加载到一个寄存器中,并将其与另一个寄存器中的值链接起来。
- 存储条件 (Store-Conditional):将一个值存储到一个内存位置,但只有当该内存位置满足某个条件时才会执行此操作。
- 交换 (Swap):交换两个寄存器中的值。
- 比较并交换 (Compare-and-Swap):将一个寄存器中的值与一个内存位置中的值进行比较,如果相等,则用另一个寄存器中的值替换内存位置中的值。
- 获取并添加 (Fetch-and-Add):将一个内存位置中的值加载到一个寄存器中,并将其与另一个寄存器中的值相加,然后将结果存储回内存位置。
- 递增 (Increment):将一个内存位置中的值递增 1。
- 递减 (Decrement):将一个内存位置中的值递减 1
5.线程安全问题
多线程程序中,因内存访问冲突或数据竞争而导致程序行为不正确或崩溃的情况。
通常发生在多个线程同时访问共享数据时。线程安全问题有:
- 数据损坏:当多个线程同时修改同一个数据时,可能会导致数据损坏。
- 死锁:当多个线程都在等待对方释放锁时,可能会导致死锁。
- 饥饿:当一个线程长时间无法获得锁时,可能会导致饥饿。
- 不可预测的行为:当多个线程同时访问共享数据时,可能会导致程序行为不可预测。
使用同步机制来保证共享数据的访问是原子的:
- 锁:锁是一种用来保护共享数据的同步机制。当一个线程获取锁后,其他线程就不能访问被锁保护的数据,直到该线程释放锁。
- 信号量:信号量是一种用来控制共享资源访问的同步机制。当一个线程获取信号量后,它就可以使用该资源。当该线程释放信号量后,其他线程就可以使用该资源。
- 原子操作:原子操作是指在一条指令执行期间不会被中断的操作。原子操作可以使用专门的原子性指令来实现,例如加载链接/存储条件指令(LL/SC)和比较并交换指令(CAS)。
6.实现线程的方式
7.Thread类 与 Runnable 接口的区别
- Thread 类继承自
Object
类,也实现了Runnable 接口。 - Thread类中包含很多线程的管理方法。Thread实际上是一个工具类(管理线程的生命周期和线程信息),而
Runnable
接口只能定义线程要执行的任务。 Thread
类是一个类,而Runnable
接口是一个接口。Thread
类提供了很多方法来控制线程的执行,比如start()
,stop()
,sleep()
等,而Runnable
接口只包含一个抽象方法run()
。Runnable
接口的实现类可以被多个线程同时执行,而Thread
类只能被一个线程执行。
Thread类的方法
start()
:启动线程。run()
:线程执行体。stop()
:停止线程。(不推荐使用)sleep(long millis)
:使线程睡眠指定毫秒数。join()
:等待线程终止。join(long millis)
:等待线程终止指定毫秒数。isAlive()
:检查线程是否还活着。isDaemon()
:检查线程是否为守护线程。setDaemon(boolean)
:设置线程是否为守护线程。getName()
:获取线程的名称。setName(String)
:设置线程的名称。getPriority()
:获取线程的优先级。setPriority(int)
:设置线程的优先级。getId()
:获取线程的ID。
献上找的网图
8.synchronized:监视器锁,监视一个对象来作为锁的依据
- 这个对象需要是多个线程同时可以看到一个对象;
- 这把锁也是Java内置的隐式锁;
- 代码执行完了就解锁,没执行完成就一直持有锁。
9.死锁问题
- 互斥条件:一个资源只能由一个线程占用。
- 占有并等待条件:一个线程占有一个资源,同时等待另一个资源。
- 不可剥夺条件:一个资源只能由占有它的线程释放。
- 循环等待条件:存在一个等待资源的线程环路,即每个线程都在等待另一个线程释放资源。
如何预防死锁
- 破坏互斥条件:允许多个线程同时访问同一个资源。
- 破坏占有并等待条件:当一个线程请求一个资源时,如果该资源已经被另一个线程占用,则让请求资源的线程等待,直到该资源被释放。
- 破坏不可剥夺条件:如果一个线程占有一个资源,但长时间没有使用该资源,则可以将该资源从该线程中剥夺,并分配给其他线程。
- 破坏循环等待条件:确保不存在等待资源的线程环路。
10.AQS 抽象队列同步器
可以访问大佬的文章,AQS 通过一个双向的先进先出(FIFO)队列(同步队列)来管理等待线程,如果某个线程发现前驱的线程释放了锁,便会获得锁。
AQS(AbstractQueuedSynchronizer)详解 - 知乎 (zhihu.com)
我们可以创建一百个线程,对num进行 ++ 操作 ,每个线程中都是一个死循环,判断跳出循环的条件是num==100w。跳出后每个线程打印本线程执行了多少次。结果是每个线程可能都不一样,有的执行得多,有的执行得少。
如果用AQS,可以保证每个线程都执行1w次,使得线程之间的竞争消耗基本上没有,保证并发性能资源浪费程度非常低。JAVA并发做得好,很大程度上在于这一个类。