Skip to content

保护性暂挂模式

更新: 3/3/2025 字数: 0 字 时长: 0 分钟

对于锁的正确理解

锁是每一个Java程序员都必然会接触到的内容,但是如何正确的理解锁是一个问题。

个人认为,对于锁的正确理解是:锁是用来保护锁住的数据,一旦数据被上锁,则在并发场景下该数据不会出现问题

举个例子

常见的对象锁,我们讲对象锁后,其他线程对该对有的操作均要等待解锁,但此时若对相同类的不同实例化对象进行操作,则可以进行操作。

同样的,如果我们想要让一个类的所有对象均高并发下稳定,则对对象对应的Class进行上锁(缺点为所有针对该类的操作全部变为串行,相当于直接地图炮)

死锁的形成

死锁的概念也很简单,就是两个线程互相请求对方持有的锁。但是我们如何认知死锁并且预防死锁呢?

一个简单的判断方式是,一旦一个线程(往往是一次网络请求)持有一个以上的锁时,就可能存在死锁问题。(因为复数个锁才可能会出现相互请求)

常见的解决方式

  • 让锁获取不到就不再等待/为等待锁上一个最大的限制时间:这个操作可以通过ReentrantLock类的tryLock方法实现,缺点为无法保证一个操作一定会完成,可以通过自旋的方式来优化,但性能也会因此降低
  • 一次性获取两个锁:我们可以通过一个中间件来放入所有可能上锁的对象,然后上锁直接等价位取出中间件中的对象,同时为了防止中间件操作出现问题,我们可能还需要对中间件采取上锁,缺点为引入中间件,性能降低,重量提升
  • 保证锁顺序获取:如果我们让复数个锁均按一个固定的顺序去取的话则可防止死锁,缺点为仅对两个线程申请的复数个锁完全一样时才生效,条件较难达成

保护性暂挂模式

针对我们拿不到锁采取自旋的方案,由于高并发环境下大量自旋对CPU压力过大,我们可以采用挂起的方式,这就是我们保护性暂挂模式的理念

java
public class GuardedQueue {



   private final Queue<Integer> sourceList;



   public GuardedQueue() {

       this.sourceList = new LinkedBlockingQueue<>();

  }



   public synchronized Integer get() {

       while (sourceList.isEmpty()) {

           try {

               wait();  //若拿不到对象,则直接挂起,等待被唤醒

          } catch (InterruptedException e) {

               e.printStackTrace();

          }

      }

       return sourceList.peek();

  }



   public synchronized void put(Integer e) {

       sourceList.add(e);

       notifyAll();  //当对象放回队列后,唤醒所有使用GuardedQueue实例的wait线程

  }

}

拓展——监控器

由于保护挂起模式实际上是使用while对一个条件监控完成了自旋,因此我们可以基于保护性暂挂模式的设计方案,我们可以自己做一个微型的监听器用于监听一个实践

java

public class Listener {


   private volatile Boolean condition=false;




   public synchronized Integer get() {

       while (!condition) {

           try {

               wait();  //若拿不到对象,则直接挂起,等待被唤醒

          } catch (InterruptedException e) {

               e.printStackTrace();

          }

      }

      //.......剩余的操作

  }


	public setCondition(Boolean condition){
		this.condition=condition;
		if(condition){
			notifyAll();
		}
	}

}
本站访客数 人次      本站总访问量