type
status
date
slug
summary
tags
category
password
icon
进程和线程
- 进程和线程
- 操作系统中运⾏多个软件
- ⼀个运⾏中的软件可能包含多个进程
- ⼀个运⾏中的进程可能包含多个线程
- 进程之间数据不共享,而同一个进程内的线程,可以共享该进程内的数据。
- CPU 线程和操作系统线程
- CPU 线程
- 多核 CPU 的每个核各⾃独⽴运⾏,因此每个核⼀个线程
- 「四核⼋线程」:CPU 硬件⽅在硬件级别对 CPU 进⾏了⼀核多线程的⽀持(本质上依然是每个核⼀个线程)
- 操作系统线程:操作系统利⽤时间分⽚的⽅式,把 CPU 的运⾏拆分给多条运⾏逻辑,即为操作系统的线程
- 单核 CPU 也可以运⾏多线程操作系统
- 线程是什么:按代码顺序执⾏下来,执⾏完毕就结束的⼀条线
多线程的使⽤
- Thread 和 Runnable
- Thread
- Runnable
- ThreadFactory
- Executor 和线程池
- 常⽤:
newCachedThreadPool()
- 短时批量处理:
newFixedThreadPool()
- Callable 和 Future
线程同步与线程安全
- synchronized
- synchronized ⽅法
- synchronized 代码块
- synchronized 的本质
- 保证⽅法内部或代码块内部资源(数据)的互斥访问。即同⼀时间、由同⼀个 Monitor 监视的代码,最多只能有⼀个线程在访问
- 保证线程之间对监视资源的数据同步。即,任何线程在获取到 Monitor 后的第⼀时间,会先将共享内存中的数据复制到⾃⼰的缓存中;任何线程在释放 Monitor 的第⼀时间,会先将缓存中的数据复制到共享内存中。
成员方法添加 synchronized 关键字,实际也会调用 synchronized(this){},这里传入的 Monitor 就是 this,而静态方法添加的 Monitor 就是当前类的 class 对象。如果括号里传入了其他值,那么 Monitor就是其他对象


- volatile
- 使用原子类型
- 加锁
- 保证加了 volatile 关键字的字段的操作具有同步性,以及对
long
和double
的操作的原⼦性(long double 原⼦性这个简单说⼀下就⾏)。因此 volatile 可以看做是简化版的 synchronized。 - volatile 只对基本类型 (byte、char、short、int、long、float、double、boolean) 的赋值操作和对象的引⽤赋值操作有效,你要修改 User.name 是不能保证同步的。
- volatile 依然解决不了 ++ 的原⼦性问题。
下面这段代码运行后,程序不会自动结束为什么? 因为子线程中会复制一份running
变量来使用,一般情况下,复制的变量不会保证同步性,也就是说主线程修改了running
变量,子线程并未实时去同步,导致子线程的循环无法结束,这个程序也无法结束。 怎么解决? 1. 给变量加volatile
关键字,这样就可以开启变量的同步性,无论哪个线程修改了变量,都会同步给其他线程,使用时也会每次获取最新的。
java.util.concurrent.atomic
包:- 下⾯有
AtomicInteger
AtomicBoolean
等类,作⽤和 volatile 基本⼀致,可以看做是通⽤版的 volatile。
- Lock / ReentrantReadWriteLock
- 同样是「加锁」机制。但使⽤⽅式更灵活,同时也更麻烦⼀些。
- ⼀般并不会只是使⽤
Lock
,⽽是会使⽤更复杂的锁,例如ReadWriteLock
:
finally 的作⽤:保证在⽅法提前结束或出现 Exception 的时候,依然能正常释放锁。
- 线程安全问题的本质:
在多个线程访问共同的资源时,在某⼀个线程对资源进⾏写操作的中途(写⼊已经开始,但还没结束),其他线程对这个写了⼀半的资源进⾏了读操作,或者基于这个写了⼀半的资源进⾏了写操作,导致出现数据错误。
- 锁机制的本质:
通过对共享资源进⾏访问限制,让同⼀时间只有⼀个线程可以访问资源,保证了数据的准确性。
- 不论是线程安全问题,还是针对线程安全问题所衍⽣出的锁机制,它们的核⼼都在于共享的资源,⽽不是某个⽅法或者某⼏⾏代码。
悲观锁和乐观锁?
在 Android 面试中经常会被问到的一个问题,实际 Android 开发中不会用到,而是后端开发中容易使用到。
悲观锁:
读取数据时,悲观的认为读取数据后数据会被其他线程修改,就在读取数据之前加上锁,等自己处理好数据后,重新把处理好的数据赋值后再释放锁,这样其他线程就只能在自己处理完数据后读取到新的数据
乐观锁:
读取数据时,乐观的认为,自己读取数据和处理数据到写入新数据的这段时间内,不会有其他线程修改数据,就不加锁,直到自己写入数据时,检查数据是否被其他人修改过,如果修改过,自己就拿修改过的数据重新进行一次计算,直到某一次,写入新数据时,没有其他线程修改过数据,就加锁,把新数据写入,然后释放锁。(这里写入时必须加锁,防止其他线程也写入,发生冲突)这样别人就能读取到新的数据。
死锁
什么是死锁?
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程 P1 正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程 P2 所占用,而 P2 在未释放打印机之前,又提出请求使用正被 P1 占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
死锁出现的场景
- 多个线程:彼此申请对方资源而导致的死锁。A申请B的资源时,因为资源被占用,A会被挂起等待B释放资源,同时B申请A的资源,因资源被占用B挂起等待A释放资源,而AB都处于挂起状态又无法释放资源,便形成了死锁。
- 单个线程:A有自己的资源,但还要申请新的资源,而新的资源被占用,则A会挂起等待,同时会保护的资源而不释放,形成死锁。
死锁产生的原因?
- 系统资源的竞争:系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
- 进程运行推进顺序不合适:进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求时,该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
- 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
避免死锁的方法:
死锁避免的基本思想:
系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
如何避免死锁?
在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。 一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方面入手 :
- 破坏请求和保持条件:在系统中不允许进程在已获得某种资源的情况下,申请其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。
- 方法一:在所有进程开始运行之前,必须一次性的申请其在整个运行过程中所需的全部资源,这样,该进程在整个运行期间便不会再提出资源请求,从而破坏了“请求”条件。系统在分配资源时,只要有一种资源不能满足进程的需要,即使其它所需的各资源都空闲也不分配给该进程,而让该进程等待,由于该进程在等待期间未占用任何资源,于是破坏了“保持”条件。
- 方法二:要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源S时,需要先把它先前占有的资源R释放掉,然后才能提出对S的申请,即使它很快又要用到资源R。
- 两种协议比较:第二种协议优于第一种协议,因为第一种协议会造成资源的严重浪费,使资源利用率大大的降低,也会由于占据大量资源导致其它进程的饥饿问题。
- 破坏不可抢占条件:允许对资源实行抢夺。
- 方式一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
- 方式二:如果一个进程请求当前被另一个进程占有的资源,则操作系统可以抢占另一个进程,要求它释放资源,只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。
- 破坏循环等待条件
- 对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申请的资源的编号大于当前编号时,才能进行申请。
死锁避免和死锁预防的区别:
死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。
死锁的解除
一旦检测出死锁,就应立即采取相应的措施,以解除死锁。死锁的解除主要有两种方法:
- 抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
- 终止或者撤销进程。终止或者撤销系统中的一个或多个死锁进程,直到打破循环环路,使系统从死锁状态解脱出来。
- 作者:shuouyang
- 链接:https://notion-tree.vercel.app/article/07561f2b-468a-47c7-8945-c1f77207e655
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。