Java 并发设计模式(二)

简介: Thread Local Storage 表示线程本地存储模式。 大多数并发问题都是由于变量的共享导致的,多个线程同时读写同一变量便会出现原子性,可见性等问题。局部变量是线程安全的,本质上也是由于各个线程各自拥有自己的变量,避免了变量的共享。

三、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 模式在条件不满足的时候,会一直等待条件满足。


相关文章
|
1天前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
16 4
|
1天前
|
设计模式 缓存 Java
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
|
1天前
|
安全 Java
如何在Java中处理并发集合
如何在Java中处理并发集合
|
1天前
|
Java 调度
Java多线程编程与并发控制策略
Java多线程编程与并发控制策略
|
1天前
|
安全 Java 开发者
Java并发编程:理解并发与多线程
在当今软件开发领域,Java作为一种广泛应用的编程语言,其并发编程能力显得尤为重要。本文将深入探讨Java中的并发编程概念,包括多线程基础、线程安全、并发工具类等内容,帮助开发者更好地理解和应用Java中的并发特性。
6 1
|
1天前
|
存储 安全 Java
深入剖析Java并发库:Exchanger的工作原理与应用场景
深入剖析Java并发库:Exchanger的工作原理与应用场景
|
1天前
|
存储 缓存 Java
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
|
18小时前
|
安全 Java 数据安全/隐私保护
解决Java中的并发访问问题
解决Java中的并发访问问题
|
1天前
|
设计模式 监控 安全
设计模式之代理模式(Java)
设计模式之代理模式(Java)
|
1天前
|
设计模式 Java 中间件
深入探索Java设计模式:责任链模式解析与实践
深入探索Java设计模式:责任链模式解析与实践
5 0