三、Guarded Suspension 模式
1. Guarded Suspension 实现
Guarded Suspension 意为保护性暂停。一个典型的使用场景是:当客户端线程 T 发送请求后,服务端这时有大量的请求需要处理,这时候就需要排队,线程 T 进入等待状态,直到服务端处理完请求并且返回结果。
Guarded Suspension 的实现很简单,有一个对象 GuardedObject,其内部有一个属性,即被保护的对象,还有两个方法,客户端调用 get() 方法,如果未获取到结果,则进入等待状态,即“保护性暂停”;还有一个 notice() 通知方法,当服务端处理完请求后,调用这个方法,并且唤醒等待中的线程。示意图如下:
示例代码如下:
public class GuardedObject<T> { private T obj; private final Lock lock = new ReentrantLock(); private final Condition finished = lock.newCondition(); //调用方线程获取结果 T get(){ lock.lock(); try { while (未获取到结果){ finished.await(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return obj; } //执行完后通知 void notice(T obj){ lock.lock(); try { this.obj = obj; finished.signalAll(); } finally { lock.unlock(); } } }
从代码中可以看到,Guarded Suspension 模式本质上就是一种等待-通知机制,只不过使用这种模式,在解决实际的问题的时候,需要根据情况进行程序功能的扩展。
2. 使用示例
还是上面提到的那个例子,当客户端发送请求后,需要等待服务端的响应结果,这时候就可以使用 Guarded Suspension 来实现,下面是代码示例:
public class SendRequest<T> { //相当于消息队列 private final BlockingQueue<Request> queue = new ArrayBlockingQueue<>(5); //客户端发送请求 void send(Request request) throws InterruptedException { //将消息存放至队列中 queue.put(request); //创建Guarded Suspension模式的对象 GuardedObject<Request> guardedObject = GuardedObject.create(request.id); //循环等待,获取结果 Request res = guardedObject.get(Objects::nonNull); } //服务端处理请求 void handle() throws InterruptedException { //从队列中获取请求 Request request = queue.take(); //调用请求对应的GuardedObject,并处理请求 GuardedObject.handleRequest(request.id, request); } //请求类 private static class Request{ private int id; private String content; } } 需要注意的是,这里并不是直接使用 new GuardedObject() 的方式来创建对象,这是因为需要找到每个请求和对象之间的对应关系,所以 GuardedObject 内部使用了一个 map 来保存对象,key 是对应的请求 id。 GuardedObject 类代码如下: public class GuardedObject<T> { private T obj; private final Lock lock = new ReentrantLock(); private final Condition finished = lock.newCondition(); private static final ConcurrentHashMap<Integer, GuardedObject> map = new ConcurrentHashMap<>(); //创建对象 public static GuardedObject create(int id){ GuardedObject guardedObject = new GuardedObject(); //保存对象和请求的对应关系 map.put(id, guardedObject); return guardedObject; } //处理请求 public static void handleRequest(int id, Object obj){ GuardedObject guardedObject = map.remove(id); if (guardedObject != null){ //具体的处理逻辑省略 //处理完后通知 guardedObject.notice(obj); } } //调用方线程获取结果 T get(Predicate<T> p){ lock.lock(); try { while (!p.test(obj)){ finished.await(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return obj; } //执行完后通知 void notice(T obj){ lock.lock(); try { this.obj = obj; finished.signalAll(); } finally { lock.unlock(); } } }
四、Balking 模式
Balking 模式的典型应用场景是,业务逻辑依赖于某个条件变量的状态,因此这种模式又可以理解为多线程版本的 if。
public class BalkingTest { private boolean flag = false; public void execute(){ if (!flag){ return; } //具体的执行操作省略 flag = false; } public void test(){ //省略业务代码若干 flag = true; } }
例如上面这个例子,一段业务逻辑会改变 flag 的值,另一个方法会根据 flag 的值来决定是否继续执行。
这个程序并不是线程安全的,解决的办法也很简单,就是加互斥锁,然后可以将改变 flag 值的逻辑单独拿出来,如下:
public class BalkingTest { private boolean flag = false; public synchronized void execute(){ if (!flag){ return; } //具体的执行操作省略 flag = false; } public void test(){ //省略业务代码若干 change(); } public synchronized void change(){ flag = true; } }
Balking 模式一般可以使用互斥锁来实现,并且可以将对条件变量的改变的逻辑和业务逻辑进行分离,这样能够减小锁的粒度,提升性能。Balking 模式大多应用于需要快速失败的场景,即当条件变量不满足,则直接失败。这也是它和 Guarded Suspension 模式的区别,因为 Guarded Suspension 模式在条件不满足的时候,会一直等待条件满足。