Java 设计模式——并发型模式整理

目录:

  • 双重检查锁定
  • 阻碍模式
  • 守卫模式
  • 线程特定存储
  • 反应器模式
  • 基于事件的异步模式

简单提及

  • 线程池模式
  • 调度模式
  • Lock 模式
  • 消息设计模式
  • 读写锁模式

双重检查锁定模式

双重检查锁定模式(也被称为"双重检查加锁优化",“锁暗示”(Lock hint)[1]) 是一种软件设计模式用来减少并发系统中竞争和同步的开销。双重检查锁定模式首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。
该模式在某些语言在某些硬件平台的实现可能是不安全的。有的时候,这一模式被看做是反模式。

可以参考之前写的博客单例设计模式

双重检查锁定模式已经被定义为了“反模式”,并且不推荐使用,这里可以通过了解一下双重检查锁定模式来一窥 JVM 对象初始化的过程与 volatile 关键字的作用。Wiki 通过实现一个单例模式来体现双重检查锁定模式,这里引用一点创建行模式 – 单例模式中的代码,正如之前在创建型模式 – 单例模式中说的,在单线程环境中不做线程安全检查的代码不会有问题

/**乞丐版懒汉单例模式**/
public class InitialSingleton {

    private static InitialSingleton singleton = null; //懒汉

    private InitialSingleton() {
		/**外部逻辑**/
    }

    public static InitialSingleton getInstance() {
        if (singleton == null) {
            return new InitialSingleton();
        }
        return singleton;
    }
}


但是在多线程中就会因为可见性与一致性问题导致程序运行不符合预期。所以就有了标准饿汉式单例模式

/**标准懒汉单例模式**/
private BasicLazySingleton(){}

private BasicLazySingleton instance = null; //饿汉

public static synchronized BasicLazySingleton getInstance() { //线程安全方法,保证了不会出现多线程同时新建单例对象的情况
    if (instance == null) {
        instance = new BasicLazySingleton();
    }
    return instance;
}


但是这种方式在第一次加载之后,以后每次获取的时候还需要获取锁,释放锁,其实是没有必要的,性能白白浪费,所以有些人就想到了使用以下方式进行优化

private BasicLazySingleton(){}

private BasicLazySingleton instance = null; //饿汉

public static synchronized BasicLazySingleton getInstance() { //线程安全方法,保证了不会出现多线程同时新建单例对象的情况
    if (instance == null) { //检查是否已经初始化过了
    	synchronized(this) { //没有初始化,获取锁
    		if (instance == null) { //再次检查变量是否已经被初始化过
    			instance = new BasicLazySingleton(); //如果依然没有,进行初始化
    		}
    	}
    }
    return instance; //返回
}

直觉上,这个算法看起来像是该问题的有效解决方案。然而,这一技术还有许多需要避免的细微问题。例如,考虑下面的事件序列:
线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有覆盖B使用的内存(缓存一致性)),程序很可能会崩溃

这个问题就在于线程 A 初始化变量到一半,就已经不为 null 了,但是这个时候它其实没有完全初始化完毕,这个时候 B 线程判断变量已经初始化完成了,直接返回,导致程序异常。
使用 volatile 或者 final 关键字可以解决这个问题,volatile 会在编译成机器指令的时候对前后的操作加上内存屏障,解决了可见性的问题,以下是使用 volatile 的方式

/**高富帅懒汉单例模式**/
public class BasicLazySingleton {

    private static boolean initialized = false; //单例初始化标志位,添加 volatile 保证其可见性
	
	private DBProperties dbProperties;

    private BasicLazySingleton() {
    	synchronized (BasicLazySingleton.class) { //保证线程安全
	        if (!initialized) { //首次初始化
	            initialized = true; //改变标志位
				
				/**业务逻辑部分**/
				dbProperties = null;
	        } else { //多线程同时初始化时,由于 volatile 内存屏障对可见性的保证,initialized 一定已经是 true 了
	            throw new RuntimeException("单例已经被初始化过"); //抛出异常
	        }
        }
    }

	//final 保证了方法不被重写
    public static final BasicLazySingleton getInstance() {
        return BasicLazySingletonHolder.LAZY;
    }

	public DBProperties getDbProperties {
		return dbProperties;
	}

	/**
	根据 Java 特性
	1、使用内部类,规避了 JVM 加载外部类的时候就单例进行初始化
	2、在外部类被调用的时候内部类才会被加载(类的懒加载)
	3、内部类必须在方法调用之前初始化(对象的懒加载,所以调用 BasicLazySingletonHolder.LAZY 之前,才会去构造 BasicLazySingleton)
	4、由 static 对外部的可见性
	5、final 保证了 LAZY 不被重写
	**/
    static class BasicLazySingletonHolder {
        private static final BasicLazySingleton LAZY = new BasicLazySingleton();
    }
}

阻碍模式

The balking pattern is a software design pattern that only executes an action on an object when the object is in a particular state. For example, if an object reads ZIP files and a calling method invokes a get method on the object when the ZIP file is not open, the object would “balk” at the request. In the Java programming language, for example, an IllegalStateException might be thrown under these circumstances.
There are some specialists in this field who consider balking more of an anti-pattern than a design pattern. If an object cannot support its API, it should either limit the API so that the offending call is not available, or so that the call can be made without limitation it should:
Be created in a “sane state”
Not make itself available until it is in a sane state
Become a facade and answer back an object that is in a sane state 摘自 Wiki

阻碍模式是一种只在对象处于某种特定状态的时候对对象执行一个操作的软件设计模式。比如,如果一个对象读取 ZIP 文件并且一个方法调用在 ZIP 文件还没有打开的时候调用了一个 get 方法,对象将会在请求中 “阻碍”。比如,在 Java 编程语言中,这种情况下就会抛出一个 IllegalStateException。

这个领域中的某些专家认为阻碍模式更像一种反模式(与双重检查锁定模式一样是设计模式的反面教材),而不是一种设计模式。如果一个对象不能支持它的 API(比如在本例中就是还没有处理好 ZIP 文件并不能返回),它应该限制 API,这样可能侵犯的调用就不存在了,或者调用可以被设计成没有限制的:

在 “清楚明晰的状态” 下被创建

知道它处于清楚明晰的状态之前都不将它构造为可用

编程一个门面,并且返回一个处于清楚明晰的状态下的对象

通过 Wiki 上的解释,可以知道阻碍模式就是一种 Blocking 编程思想,直到对象完全处理完才允许外部对它的调用。

/**
 * 这里使用 Wiki 的例子
 * 以下是一个一般的,简单的阻碍模式的实现。正如前文所述,注意 “synchronzied” 关键字是如何使用的。如果有
 * 多个线程调用 job 方法,只有一个可以操作,同时其他的调用将会什么都不返回。另一个需要注意的是 
 * jobCompleted() 方法。它被设置为线程安全是因为唯一能保证另一个线程能看到一个属性改变的唯一方法就是对所有
 * 能进入它的入口线程安全话或者将它设为 volatile。
 **/
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        Job job = new Job();
        CountDownLatch countDownLatch = new CountDownLatch(10); //定义倒计时
        for (int i = 0; i < 10; i++) {
            startThread(job, countDownLatch);
        }
        countDownLatch.await(); //阻塞,直到countDown结束
        System.out.println("执行完毕");
    }

    private static void startThread(Job job, CountDownLatch countDownLatch) {
        new Thread(() -> { //递减
            try {
                job.job();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }).start();
    }
}


class Job {
    private boolean jobInProgress = false; //job 方法正在操作标志位

    /**job 方法**/
    public void job() throws InterruptedException {
        System.out.println(Thread.currentThread() + " 线程进入 job 方法");
        synchronized(this) { //获取调用 job() 方法对象的锁,将其他线程想要获取这个对象的锁时,将会被阻塞
            if (jobInProgress) { //如果 job 方法正在操作标志位为 true
                System.out.println("job 方法正在被调用," + Thread.currentThread() + " 线程虽然获取了锁,但是不能进行操作");
                return; //直接返回
            }
            jobInProgress = true; //否则置为 true
        }
        //执行 job 方法的代码,当进行这段代码时,其他获取该对象的线程可以获取到锁,但是调用 job 方法时都将直接返回
        //...
        System.out.println(Thread.currentThread() + " 线程可以操作 job 方法,开始操作");
        Thread.sleep(10000);
        jobCompleted();
    }

    /**jobComplete 方法**/
    void jobCompleted() {
        synchronized(this) { //获取调用 jobCompleted() 方法对象的锁,将其他线程想要获取这个对象的锁时,将会被阻塞
            System.out.println("job 方法操作结束," + Thread.currentThread() + " 结束操作");
            jobInProgress = false; //重置方法正在操作标志位为 false,直到这时其他线程调用 job 方法才有可能进入 job 真正的操作部分
        }
    }
}

运行结果如下:

Thread[Thread-0,5,main] 线程进入 job 方法
Thread[Thread-2,5,main] 线程进入 job 方法
Thread[Thread-1,5,main] 线程进入 job 方法
Thread[Thread-3,5,main] 线程进入 job 方法
Thread[Thread-4,5,main] 线程进入 job 方法
job 方法正在被调用,Thread[Thread-2,5,main] 线程虽然获取了锁,但是不能进行操作
Thread[Thread-0,5,main] 线程可以操作 job 方法,开始操作
job 方法正在被调用,Thread[Thread-4,5,main] 线程虽然获取了锁,但是不能进行操作
Thread[Thread-5,5,main] 线程进入 job 方法
job 方法正在被调用,Thread[Thread-3,5,main] 线程虽然获取了锁,但是不能进行操作
Thread[Thread-6,5,main] 线程进入 job 方法
Thread[Thread-7,5,main] 线程进入 job 方法
Thread[Thread-8,5,main] 线程进入 job 方法
job 方法正在被调用,Thread[Thread-1,5,main] 线程虽然获取了锁,但是不能进行操作
Thread[Thread-9,5,main] 线程进入 job 方法
job 方法正在被调用,Thread[Thread-8,5,main] 线程虽然获取了锁,但是不能进行操作
job 方法正在被调用,Thread[Thread-7,5,main] 线程虽然获取了锁,但是不能进行操作
job 方法正在被调用,Thread[Thread-6,5,main] 线程虽然获取了锁,但是不能进行操作
job 方法正在被调用,Thread[Thread-5,5,main] 线程虽然获取了锁,但是不能进行操作
job 方法正在被调用,Thread[Thread-9,5,main] 线程虽然获取了锁,但是不能进行操作
job 方法操作结束,Thread[Thread-0,5,main] 结束操作
执行完毕

测试方法中添加了 10 个线程,并同时启动对 job 对象的 job() 方法进行操作,除了最后两行其他行几乎是同时打印出的,最后一行在 10 秒后打印出(Thread.sleep(10))

这种模式可以解决 Java 中***多线程可以同时访问的,某些需要长时间操作,不确定何时能够执行完成,并且不会因为未执行完成而阻碍其他对象对它的获取但是可能会返回状态异常*** 的操作 。

守卫模式

可以参考之前写的博客:JAVA守护线程

/**线程类**/
public class DaemonThread implements Runnable {

    public void run() {
        try {
            Thread.sleep(1000); //睡眠 1 秒
            System.out.println(Thread.currentThread() + " 线程还在执行中");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Test {

    public static void main(String[] args) {
        Runnable r = new DaemonThread();
        Thread t = new Thread(r);
        t.setDaemon(true); //设置守护线程
        t.start(); //运行线程
    }
}

运行会发现并没有打印出 “线程还在执行中” 的结果,说明在 main 方法线程运行结束的时候,被标记为守护现车个的子线程也结束了,这就是守护线程,通过在自线程运行前 setDaemon(true) 来开启守护模式。另外 Wiki 线程页面 Java 线程的定义中有一条提到守护线程会在非守护线程都结束时自动终止。这里先了解一些守护线程是什么:也称为服务线程,在没有用户线程时自动结束,主要是为用户线程提供服务的,比如 Java 程序的垃圾回收线程。它们是运行在后台,独立于终端的一种线程,依赖于系统,不依赖于终端。守护线程在程序中只有守护线程,没有用户线程的情况下会结束。

用户线程是什么:用户线程就是用户自己创建的,比如业务相关的线程,所有在 Java 中可控的线程都可以被称为用户线程。

由这些定义就可以明白了,通过 setDaemon 将 main 方法中启动的自线程标记成守护线程,这样当 main 线程跑完的时候发现程序中只有守护线程了,就自动关闭了。

线程特定存储模式

***本地线程存储(TLS)模式***是计算机编程方法中那些使用静态或者全局内存作为线程本地变量的模式。

使用全局变量通常是在现代编程中不推荐的,类似于 UNIX 操作系统是被设计成为单核硬件并且需要额外的装置来保留 预先-重入 APIs 的语义。一个这种情形的例子是当功能使用一个全局变量来设置一个错误条件(比如 C 库中很多功能使用的全局变量 errno)。如果 errno 是一个全局变量,一个系统功能线程的调用可能重写了之前被一个不同的线程设置的值,可能早于接另一个线程可以检查错误条件的接下来的代码的时间。方案是让 errno 称为一个看上去像是全局变量的变量,但是实际上存在在每一个线程中—比如,它存活于一个本地线程存储中。第二个使用案例是多线程在一个全局变量中累加信息。为了避免竞争,每个进入这个全局变量的线程将不得不收到一个 互斥锁的保护。替代方法是,每个线程可以在一个本地线程变量中累加(通过定义,不能被其他线程读取或者写入,暗示了将不会出现竞争)。多个线程将只需要对最终累加进行线程安全化,将它们自己本地线程中的变量累加到一个单独的,真正的全局变量。

许多系统强制限制了本地线程内存快的大小,事实上总是有点少。另一方面,如果一个系统可以提供至少一个内存地址(指针)大小的本地线程变量,那么这就允许了在本地线程方式中使用任何内存块大小的内存,通过在本地线程变量中分配一个动态的,保存了块的内存地址的内存块。

通过 Wiki 对于线程特定存储的定义,可以知道所谓线程特定存储,其实就是本地线程(Thread Local)变量,这种变量的出现是为了解决两种为题,第一种,当有些变量的含义是全局性的,但是自身不一定需要严格的遵守全局线程安全(可能是因为对象设计本身没有保证全局线程安全的能力,也可能是为了避免频繁的获取,释放锁),第二种就是对于一些可控的,依靠在不同的线程中累积得到的全局变量进行分治,通过建立本地线程变量,并在最后累加时进行线程安全化操作,来避免更新这个全局变量时频繁的线程安全化操作。还有一种问题这里没有提到,就是依靠线程的独立性,作为容器保存一些线程相关的变量,并在未来的某个时间再获取出来。

Java 中也经常用这种变量,比如数据库连接(解决第一种问题),以及 PageHelper 中的分页(解决第三种问题)等等。Java 中对于本地线程存储模式的最佳实践是 ThreadLocal 类,对于 ThreadLocal 的进一步分析将在 并发编程 篇中进行。

反应器模式

可以参考之前写的博客:Java NIO 系列文章之 浅析Reactor模式

要了解这种模式需要先了解 Reactive 响应式编程的概念,这个概念将在 并发编程 篇中介绍。这里暂时引用 pjmike_pj 的博客 做简要介绍。在 Java 中的最佳实践是 reactor 包中的 Mono 和 Flux,都是作为 SpringMVC 串行式处理的异步处理方案,Mono 返回 0-1 个元素(类似于 Optional),Flux 返回 0-N 个元素(类似于 Iterator 或者 Collection)。以下是 Mono 的使用方式:

/**建立 Model**/
public class Company {

    private String name;

    private Double longitude;

    private Double latitude;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getLongitude() {
        return longitude;
    }

    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    public Double getLatitude() {
        return latitude;
    }

    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", longitude=" + longitude +
                ", latitude=" + latitude +
                '}';
    }
}

/**容器类,相当于 Dao**/
@Repository
public class CompanyRepository {
    
    @Transactional
    public void save(Company company) {
        
    }
}

/**传统控制器类**/
@RestController
public class CompanyController {

  	/**容器对象**/
    private final CompanyRepository companyRepository;

  	/**线程池**/
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

  	/**容器对象注入**/
    @Autowired
    private CompanyController(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

  	/**post 存储方法**/
    @PostMapping("/web/mvc/company/save")
    public Boolean save(@RequestBody Company company) throws ExecutionException, InterruptedException {
        /**启动线程**/
        Future<Boolean> future = executorService.submit(() -> {
          	/**线程具体操作**/
            return companyRepository.save(company);
        });
        System.out.println("Company Controller Thread name is " + Thread.currentThread().getName());
      	/**获取线程执行结果**/
        return future.get();
    }
}

/**Web Flux 调用方式**/
@Component
public class CompanyHandler {

    private CompanyRepository companyRepository;
    
    @Autowired
    private CompanyHandler(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

    public Mono<ServerResponse> save(ServerRequest serverRequest) {
        /**在 Spring Web MVC 中使用 @RequestBody
        在 Spring Web Flux 使用 ServerRequest
        Mono<User> 类似于 Optional<User>**/
        System.out.println("User Handler Thread name is " + Thread.currentThread().getName());
        Mono<Company> userMono = serverRequest.bodyToMono(Company.class);
        /**map 相当于转换工作**/
        Mono<Boolean> booleanMono = userMono.map(companyRepository::save);
        return ServerResponse.ok().body(booleanMono, Boolean.class);
    }
}

/**Web Flux url 配置**/
@Configuration
public class WebFluxConfiguration {

    @Bean
    public RouterFunction<ServerResponse> saveUser(CompanyHandler companyHandler) {
        System.out.println("Flux User Thread name is " + Thread.currentThread().getName());
      	/**路由**/
        return route(POST("/web/flux/company/save"), companyHandler::save);
    }
}

基于事件的异步模式

In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option. 摘自 Wiki

在多线程计算机编程紫红,***异步方法调用(AMI)***,也并称为 异步方法调用 或者 异步模式,是一种当等待被调用的代码结束调用的过程中站点没有被阻塞的设计模式。取而代之的是,调用线程当回应到达时被告知。为一个回应投票是一种不被不被推荐的选项。

所谓同步就是在一个线程中操作,异步就是启动不同的线程进行操作,这里不要与阻塞非阻塞搞混了,阻塞与非阻塞是针对程序顺序时序的一种描述,阻塞代表必须等待某一操作完成之后才能进行后续操作,非阻塞则表示可以延后出的,程序可能会先执行后面的代码直到被通知执行(基本上是通过回调)。Java 中典型的实践比如 AsyncContext,通过 HttpServletRequest#startAsync() 方法打开异步支持,然后对 AsyncContext 添加 AsyncListener,这样调用一个 Servlet 的方法就是非阻塞异步处理了。

public class AsyncServlet extends HttpServlet {
    
    protected void doGet(HttpServletRequest req, HttpServletResponse rep) {
        /**开启异步支持**/
        AsyncContext asyncContext = req.startAsync();
        asyncContext.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                System.out.println("完成逻辑");
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                System.out.println("超时逻辑");
            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
                System.out.println("错误逻辑");
            }

            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
                System.out.println("开始逻辑");
            }
        });
    }
}

更多基于实践的异步模式的内容将在 并发编程 篇中展开。

以下几种模式在 Java 并发模式中同样重要,但是由于太过于抽象或者设计到的代码将会很多所以这里只做一些概念性的介绍

线程池模式

In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model,[1] a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks.[2] The number of available threads is tuned to the computing resources available to the program, such as parallel processors, cores, memory, and network sockets.[3]

A common method of scheduling tasks for thread execution is a synchronized queue, known as a task queue. The threads in the pool remove waiting tasks from the queue, and place them into a completed task queue after completion of execution. 摘自 Wiki

在计算机编程中,一个 线程池 是一种为了在一个计算机程序中达到并行执行目地的软件设计模式。有时也被称为一种 replicated workers 或者 worker-crew model,一个线程池维护了多个等待被监管程序分配并行执行任务的多个线程。在维护一个多线程池时,这种模型增加了性能并且避免了在执行过程中因为频繁地创建和销毁线程的短存活任务带来的等待时间(性能损耗)。(线程池中)可用的线程数量被转换为程序可见的计算资源,就像并行处理器,内核,内存和网络端口。

一种通用的为线程执行提供的调度任务的方法是一个线程安全的队列,被称为一个任务队列。线程池中的线程从队列中移除正在等待的任务,并且在执行完成后将它们放入一个已完成任务队列。

这里提到两点,第一点,线程池维护了多个线程,目的为了避免频繁建立线程的性能损耗,第二点,线程池通过两个队列来执行任务,从等待队列中获取任务,执行完后放入完成队列,通过 Wiki 上的图可能看的更清楚一些

在这里插入图片描述
Java 中同样在 JUC 中提供了业界最佳实现的线程池类,ExecutorService 与 Executors,通过与 Callable 类的联合使用构建线程,执行线程,获得线程返回结果,有关它们的具体使用与分析将在 并发编程 篇中进行介绍。

调度模式

In computing, scheduling is the method by which work is assigned to resources that complete the work. The work may be virtual computation elements such as threads, processesor data flows, which are in turn scheduled onto hardware resources such as processors, network links or expansion cards.

A scheduler is what carries out the scheduling activity. Schedulers are often implemented so they keep all computer resources busy (as in load balancing), allow multiple users to share system resources effectively, or to achieve a target quality of service. Scheduling is fundamental to computation itself, and an intrinsic part of the execution model of a computer system; the concept of scheduling makes it possible to have computer multitasking with a single central processing unit (CPU). 摘自 Wiki

在计算中,调度模式 是一种需要分配资源来完成工作的工作。这种工作可能是虚拟计算元素,比如线程,进程或者流,那些依次在硬件资源上被调用的,比如多个进程,多个网络链接或者扩展卡。

一个调度器携带了调度动作。调度器通常已经比诶实现了,所以它们让所有的计算机资源保持繁忙(比如在负载均衡中),允许多个用户高效地分享系统资源,或者来达到一个服务质量目标。调度器是计算本身的基础,也是计算机系统执行模型本质的一个部分;调度器的概念使它可以通过单个中央处理单元(CPU)来操作多个任务。

读写锁模式

In computer science, a readers–writer (RW) or shared-exclusive lock (also known as a multiple readers/single-writer lock[1], a multi-reader lock[2], a push lock[3], or an MRSW lock) is a synchronization primitive that solves one of the readers–writers problems. An RW lock allows concurrent access for read-only operations, while write operations require exclusive access. This means that multiple threads can read the data in parallel but an exclusive lock is needed for writing or modifying data. When a writer is writing the data, all other writers or readers will be blocked until the writer is finished writing. A common use might be to control access to a data structure in memory that cannot be updated atomically and is invalid (and should not be read by another thread) until the update is complete. 摘自 Wiki

在计算机科学中,一个 读写 或者 可共享的 锁(也被称为 多读/单写 锁,一个 多读 锁,一个 推送锁,或者一个 MRSW 锁)是一个解决了多读多写问题的原始的线程安全方式。一个 RW 锁允许只读操作同时访问,同时写操作是需要可执行权限的。者意味着多线程可以并行的读取这个数据但是需要一个可执行锁来写入或者修改数据。当一个写者在写入数据时候,所有其他的写者或者读者都将被阻塞,知道这个写者完成写操作。一个通用的使用方式可能是为了控制进入内存中不能被自动更新的数据结构的权利,并且知道更新完成它都不存在(而且不应该被另一个线程准备好)。

其实这与数据库的事务机制很类似,读写锁实现了同时只有一个线程可以写对象,并且当一个对象在被写时,其他线程对它的读和写都将被阻塞,当对象没有被写的时候,所有线程都可以读它。其实就是保证对象对所有线程的一致性与可见性,从这一点上来说 volatile 关键字就可以做到,另外在 Java JUC 中还专门提供了 ReadWriteLock 来实现读写锁模式的功能。这一部分将在 并发编程 中进行介绍。

Lock 模式

In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy. 摘自 Wiki

在计算机科学中个,一个锁或者互斥(从共有的执行)是一种用来强制限制在有许多线程执行的环境中访问一个资源的线程安全化机制。一个锁被设计用来强制一个共有的并行控制策略/

其实就是一个能够解决并行环境下的几个问题(一致性有序性可见性)的解决方案的统一名称。比如 sycnhonrized,ReetrantLock 都属于 Java 最佳业界实践的 Lock 模式。关于更多 Lock 模式的内容将在 并发编程 篇中展开

消息设计模式

In software architecture, a messaging pattern is a network-oriented architectural pattern which describes how two different parts of a message passing system connect and communicate with each other.

In telecommunications, a message exchange pattern (MEP) describes the pattern of messages required by a communications protocol to establish or use a communication channel. There are two major message exchange patterns — a request–response pattern, and a one-way pattern. For example, HTTP is a request–response pattern protocol, and UDP is a one-way pattern.

在软件架构中,一种 消息模式 是一种描述了一个消息发送系统的两个不同部分如何与对方连接和通信的网络方向的架构模式。

在电信学中,一个 消息交换模式(MEP) 描述了沟通协议为了建立或者使用一个沟通通道需要的消息模式。有两个主要的消息交换模式—一种是请求-应答模式,还有一种是单程模式。比如,HTTP 是请求-应答模式协议,而 UDP 是一种单程模式。

至此设计模式篇就全部结束了,以后可能会对某些设计模式又有新的感悟时再去单独的写,设计模式毕竟不是一直会用到的东西,记不住也是很正常的,这里胡写一篇打油诗方便记忆:
设计模式有哪些?创建结构和行为,还有高阶叫并发;
创建模式有哪些?工厂单例和原型,还有对象池DI;
结构模式有哪些?适配修饰和组合,还有桥接和代理;
行为模式有哪些?责任命令迭代器,观察策略和模版;
并发模式有哪些?阻碍守卫线程池,线程存储反应器。

————————————————
版权声明:本文为CSDN博主「十幂强心」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wufeiova/article/details/92252464

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值