Synchronized(同步锁)
思考一个问题,锁是为了解决什么问题,锁的本质是什么?
锁的本质是在多线程使用共享资源中,为了解决线程资源抢占提出的一个概念。
锁的使用
public class Test {
// 修饰静态方法 (类锁)
static synchronized void test(){
}
// 修饰非静态方法 (对象锁)
synchronized void method(){
// 临界区
}
Object obj = new Object();
void demo(){
// 修饰代码块 (对象锁)
synchronized (obj){
// 临界区
}
}
}
synchronized三种加锁方式
1.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
2.修饰静态方法,作用与当前类对象加锁,进入同步代码前要获得当前类对象的锁。
3.修饰代码块,对给定对象加锁,进入同步代码前要获得给定对象的锁。
锁的升级 偏向锁-> 轻量级锁->重量级锁
偏向锁
大多情况下,锁不存在多线程竞争,总由同一个线程多次获取。
当一个线程访问了加了同步锁的代码块时候,对象头中存储当前的线程ID,后续这个线程进入和退出这段加了锁的代码快时候,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁。
偏向锁是为了在无多线程竞争的情况尽量减少不必要的轻量级锁执行路径。(偏向锁的目的是消除数据在无竞争的情况下同步,进一步提高程序的运行性能)
轻量级锁
如果偏向锁被关闭或者当前对象偏向锁已经被其他线程获取了,那么这个时候如果有线程去抢占同步锁,锁会升级为轻量级锁。
重量级锁
多个线程竞争同一个把锁的时候,虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。
Java线程的阻塞和唤醒都是依靠操作系统来完成的,os pthread_mutex_lock();
升级为重量级锁,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。
总结
偏向锁只有在第一次请求时采用CAS在锁对象的标记中记录当前线程的地址,在之后该线程再次进 入同步代码块时,不需要抢占锁,直接判断线程ID即可,这种适用于锁会被同一个线程多次抢占的情况。
轻量级锁才用CAS操作,把锁对象的标记字段替换为一个指针指向当前线程栈帧中的 LockRecord,该工件存储锁对象原本的标记字段,它针对的是多个线程在不同时间段内申请通一把锁的情况。
重量级锁会阻塞、和唤醒加锁的线程,它适用于多个线程同时竞争同一把锁的情况。
死锁是什么?产生死锁的四个必要条件?
什么是死锁?
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
产生的原因?
1. 系统资源的竞争。
系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
2. 进程运行推进顺序不合适。
进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
产生死锁的四个必要条件
互斥、占有且等待、不可抢占、循环等待。
互斥
一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
占有且等待
已知拥有资源X,和Y,线程T1,线程T2
线程T1已经抢占资源X,又想抢占资源Y,但是Y资源已经被线程T2抢占,这时T1线程处于等待Y资源的同时,又不释放资源X。
如何破坏 占有且等待
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
不可抢占
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
如何破坏 不可抢占
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。
循环等待
已知拥有资源X,和Y,线程T1,线程T2
T1拥有资源X等待Y释放
T2拥有资源Y等待X释放
如何破坏循环等待
可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源