锁
互斥锁和自旋锁
互斥锁是一种独占锁,比如当线程A加锁成功后,此时互斥锁已经被线程A独占了,只要线程A没有释放手中的锁,线程B加锁就会失败,于是就会释放CPU让给其他线程,既然线程B释放掉了CPU,自然线程B加锁的代码就会被阻塞。
对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。这在一定程度上简化了使用锁的难度,但是存在一定的开销成本:
- 当线程加锁失败时,内核会把线程的状态从运行状态设置为睡眠状态,然后把CPU切换给其他线程运行;
- 当锁被释放时,之前睡眠状态的线程会变为就绪状态,然后内核会在合适的时间,把CPU切换给该线程运行。
但是,如果被锁住的代码执行时间很短,就不该用互斥锁,而应该选用自旋锁,否则使用互斥锁。
自旋锁是通过CPU提供的CAS函数,在用户态完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁会更快、开销小。
自旋锁与互斥锁使用层面比较相似,但是实现层面完全不同:当加锁失败时,互斥锁用线程切换来应对,自旋锁则用忙等待来应对。
读写锁
读写锁适用于明确区分读操作和写操作的场景。
读多写少的场景。
乐观锁与悲观锁
互斥锁、自旋锁、读写锁都属于悲观锁。悲观锁做事比较悲观,他认为多线程同时修改共享资源的概率比较高,很容易冲突,所以访问共享资源前先要上锁。
相反,乐观锁比较乐观,它假定冲突的概率很低,先修改完共享资源,再验证这段时间内有没有发生冲突,如果 没有其他线程在修改资源,那么操作完成,如果有其他线程已经修改过这个资源,就放弃本次操作。
死锁
基础介绍
死锁是由于两个或以上线程并行执行的时候,争夺资源而相互等待造成的。
死锁只有同时满足以下四个条件才会发生:
-
互斥条件
- 是指多个线程不能同时使用同一个资源。
- 是指多个线程不能同时使用同一个资源。
-
持有并等待条件
- 当线程A已经持有了资源1,又想申请资源2,而资源2已经被线程C持有,所以线程A就会处于等待状态,但是线程A在等待的同时不会释放已经持有的资源1 。
- 当线程A已经持有了资源1,又想申请资源2,而资源2已经被线程C持有,所以线程A就会处于等待状态,但是线程A在等待的同时不会释放已经持有的资源1 。
-
不可剥夺条件
- 当线程A已经持有了资源,在自己使用完之前不能被其他线程获取
- 当线程A已经持有了资源,在自己使用完之前不能被其他线程获取
-
环路等待条件
- 在死锁发生的时候,两个线程获取资源的顺序构成了环形
- 在死锁发生的时候,两个线程获取资源的顺序构成了环形
避免死锁发生
上面说了,死锁必须满足4个条件。那么,避免死锁的解决办法就是,破坏其中一个条件就行。