【高并发】面试官:讲讲高并发场景下如何优化加锁方式?
写在前面
很多时候,我们在并发编程中,涉及到加锁操作时,对代码块的加锁操作真的合理吗?还有没有需要优化的地方呢?
问题阐述
产生死锁时的四个必要条件,只有四个条件同时具备时才能发生死锁。其中,我们在阻止请求与保持条件时,采用了一次性申请所有的资源的方式。例如在我们完成转账操作的过程中,我们一次性申请账户A和账户B,两个账户都申请成功后,再执行转账的操作。其中,在我们实现的转账方法中,使用了死循环来循环获取资源,直到同时获取到账户A和账户B为止,核心代码如下所示。
//一次申请转出账户和转入账户,直到成功
while(!requester.applyResources(this, target)){
//循环体为空
;
}
如果ResourcesRequester类的applyResources()方法执行的时间非常短,并且程序并发带来的冲突不大,程序循环几次到几十次就可以同时获取到转出账户和转入账户,这种方案就是可行的。
但是,如果ResourcesRequester类的applyResources()方法执行的时间比较长,或者说,程序并发带来的冲突比较大,此时,可能需要循环成千上万次才能同时获取到转出账户和转入账户。这样就太消耗CPU资源了,此时,这种方案就是不可行的了。
那么,有没有什么方式对这种方案进行优化呢?
问题分析
既然使用死循环一直获取资源这种方案存在问题,那我们换位思考一下。当线程执行时,发现条件不满足,是不是可以让线程进入等待状态?当条件满足的时候,通知等待的线程重新执行?
也就是说,如果线程需要的条件不满足,我们就让线程进入等待状态;如果线程需要的条件满足时,我们再通知等待的线程重新执行。这样,就能够避免程序进行循环等待进而消耗CPU的问题。
那么,问题又来了!当条件不满足时,如何实现让线程等待?当条件满足时,又如何唤醒线程呢?
不错,这是个问题!不过这个问题解决起来也非常简单。简单的说,就是使用线程的等待与通知机制。
线程的等待与通知机制
我们可以使用线程的等待与通知机制来优化阻止请求与保持条件时,循环获取账户资源的问题。具体的等待与通知机制如下所示。
执行的线程首先获取互斥锁,如果线程继续执行时,需要的条件不满足,则释放互斥锁,并进入等待状态;当线程继续执行需要的条件满足时,就通知等待的线程,重新获取互斥锁。
那么,说了这么多,Java支持这种线程的等待与通知机制吗?其实,这个问题问的就有点废话了,Java这么优秀(牛逼)的语言肯定支持啊,而且实现起来也比较简单。
Java实现线程的等待与通知机制
实现方式
其实,使用Java语言实现线程的等待与通知机制有多种方式,这里我就简单的列举一种方式,其他的方式大家可以自行思考和实现,有不懂的地方也可以问我!
在Java语言中,实现线程的等待与通知机制,一种简单的方式就是使用synchronized并结合wait()、notify()和notifyAll()方法来实现。
实现原理
我们使用synchronized加锁时,只允许一个线程进入synchronized保护的代码块,也就是临界区。如果一个线程进入了临界区,则其他的线程会进入阻塞队列里等待,这个阻塞队列和synchronized互斥锁是一对一的关系,也就是说,一把互斥锁对应着一个独立的阻塞队列。
在并发编程中,如果一个线程获得了synchronized互斥锁,但是不满足继续向下执行的条件,则需要进入等待状态。此时,可以使用Java中的wait()方法来实现。当调用wait()方法后,当前线程就会被阻塞,并且会进入一个等待队列中进行等待,这个由于调用wait()方法而进入的等待队列也是互斥锁的等待队列。而且,线程在进入等待队列的同时,会释放自身获得的互斥锁,这样,其他线程就有机会获得互斥锁,进而进入临界区了。整个过程可以表示成下图所示。
具体实现
实现逻辑
选择哪个互斥锁
线程执行转账操作的条件
线程什么时候进入等待状态
什么时候通知等待的线程执行
while(不满足条件){
wait();
}
实现代码
public class ResourcesRequester{
//存放申请资源的集合
private List
public class ResourcesRequesterHolder{
private ResourcesRequesterHolder(){}
public static ResourcesRequester getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private ResourcesRequester singleton;
Singleton(){
singleton = new ResourcesRequester();
}
public ResourcesRequester getInstance(){
return singleton;
}
}
}
public class TansferAccount{
//账户的余额
private Integer balance;
//ResourcesRequester类的单例对象
private ResourcesRequester requester;
public TansferAccount(Integer balance){
this.balance = balance;
this.requester = ResourcesRequesterHolder.getInstance();
}
//转账操作
public void transfer(TansferAccount target, Integer transferMoney){
//一次申请转出账户和转入账户,直到成功
requester.applyResources(this, target))
try{
//对转出账户加锁
synchronized(this){
//对转入账户加锁
synchronized(target){
if(this.balance >= transferMoney){
this.balance -= transferMoney;
target.balance += transferMoney;
}
}
}
}finally{
//最后释放账户资源
requester.releaseResources(this, target);
}
}
}
notify()和notifyAll()的区别
notify()方法
notifyAll()方法
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️