我相信大家都很熟悉DCL,对于缺少实践经验的程序开发人员来说,DCL的学习基本限制在单例模式,但我发现在高并发场景中会经常遇到需要用到DCL的场景,但并非用做单例模式,其实DCL的核心思想和CopyOnWrite很相似,就是在需要的时候才加锁;为了说明这个观点,我先把单例的经典代码防止如下:

  先说明几个关键词:

  volatile:保证线程的可见性,有序性;这两点非常重要,可见性让线程可以马上获释主存变化,二有序性避免指令重排序出现问题;

public class Singleton {
//通过volatile关键字来确保安全
private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}

  大家可以知道,这段代码是没有性能瓶颈的线程安全(当然,用了volatile是有一定的性能影响,但起码不需要竞争锁);这代码只会在需要的时候才加锁,这就是DCL的需要时加锁的特性,由第一个检查check保证(也就是if (singleton == null));

  但DCL的需要时才加锁的魅力不仅仅如此场景而已,我们看一个需求:一个不要求实时性的更新,所有线程公用一个资源,而且只有满足某个条件的时候才更新,那么多线程需要访问缓存时,是否需要加锁呢?不需要的,看如下代码:

private static volatile JSONArray cache = new JSONArray(Collections.synchronizedList(new LinkedList<>()));

public static int updateAeProduct(JSONObject aeProduct,String productId,boolean isFlush){
JSONObject task = new JSONObject();
String whereStr ="{\"productId\": {\"operation\": \"eq\", \"value\":\""+productId+"\" },\"provider\":{\"operation\": \"eq\", \"value\":\"aliExpress\" }}";
task.put("where",JSON.parseObject(whereStr));
task.put("params",aeProduct);
cache.add(task);
if(cache.size()>2 ||isFlush){
// 争夺更新权
JSONArray temp=cache;
synchronized (updateLock){
if(temp==cache&&cache.contains(task)){
cache = new JSONArray(Collections.synchronizedList(new LinkedList<>()));
}else {
return 1;
}
}
// 拥有更新权的继续更新
try {
Map<String,String> headers = new HashMap<>();
headers.put("Content-Type","application/json");
String response = HttpUtils.post(updateapi,temp.toJSONString(),headers);
JSONObject result = JSON.parseObject(response);
if(result!=null&&"Success".equals(result.getString("msg"))){
// System.out.println("=========================完成一次批量存储,成功Flush:"+temp.size());
}
} catch (Exception e) {
System.out.println("更新丢失,策略补救");
e.printStackTrace();
}
}
return 1;
}

  这样保证了性能,也做到了缓存的线程安全;这就是单例的厉害;我在项目中经常遇到该类场景,下面给出一个任务计时器的代码:

package com.mobisummer.spider.master.component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong; public class RateCalculator { ConcurrentHashMap<String,AtomicLong> taskInfo = new ConcurrentHashMap(); volatile boolean isStart =false; Object lock = new Object(); AtomicLong allCount = new AtomicLong(); private ScheduledExecutorService scheduledThreadPool; public void consume(Long num,String taskId){
if(taskInfo.containsKey(taskId)){
taskInfo.get(taskId).addAndGet(num);
}else {
calculateTask(num,taskId);
}
allCount.addAndGet(num);
calculateAll(num,taskId);
} /**
* 计算任务
* @param num
* @param taskId
*/
private void calculateTask(Long num,String taskId){
synchronized (lock){
if(taskInfo.containsKey(taskId)){
return;
}else {
taskInfo.put(taskId,new AtomicLong());
Thread countor = new Thread(new Runnable() {
@Override
public void run() {
while (true){
double startTime =System.currentTimeMillis();
double startCount = taskInfo.get(taskId).get();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("计数器失效");
}
double endTime =System.currentTimeMillis();
double endCount = taskInfo.get(taskId).get();
double percent =(endCount-startCount)/((endTime - startTime)/1000);
// System.out.println("目前总成功爬取速率:==========="+percent+"=======目前处理总数========:"+allCount);
System.out.println("目前"+taskId+"成功爬取速率:==========="+percent+"=======目前"+taskId+"处理总数========:"+endCount);
}
}
});
countor.start();
}
}
} /**
* 计算所有任务
* @param num
* @param taskId
*/
private void calculateAll(Long num,String taskId){
if(isStart){
return;
}else {
synchronized (this){
if(isStart){
return;
}else {
isStart =true;
Thread countor = new Thread(new Runnable() {
@Override
public void run() {
while (true){
double startTime =System.currentTimeMillis();
double startCount = allCount.get();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("计数器失效");
}
double endTime =System.currentTimeMillis();
double endCount = allCount.get();
double percent =(endCount-startCount)/((endTime - startTime)/1000);
System.out.println("目前总成功爬取速率:==========="+percent+"=======目前处理总数========:"+allCount);
// System.out.println("目前"+taskId+"成功爬取速率:==========="+percent+"=======目前"+taskId+"处理总数========:"+allCount);
}
}
});
countor.start();
}
}
}
}
}

  同样的,线程安全的双重检测,这就是DCL的魅力;

DCL并非单例模式专用的更多相关文章

  1. DCL之单例模式

    所谓的DCL 就是 Double Check Lock,即双重锁定检查,在了解DCL在单例模式中如何应用之前,我们先了解一下单例模式.单例模式通常分为"饿汉"和"懒汉&q ...

  2. Android设计模式——单例模式

    1.单例模式就是确保一个类,只有一个实例化对象,而且自行实例化并向整个系统提供这个实例. 2.使用场景: 确保某个类,有且只有一个对象,避免产生对个对象,消耗过多的资源. 2.实现单例模式的重要点: ...

  3. volatile 关键字精讲

    1.错误案例 通过一个案例引出volatile关键字,例如以下代码示例 : 此时没有加volatile关键字两个线程间的通讯就会有问题 public class ThreadsShare { priv ...

  4. 图文:TF卡和SD卡的区别及什么是TF卡?什么是SD卡

    小型存储设备凭借低廉的价格.多样化的品种.实用等特性大量充斥在大家身边,比如智能手机手机上.数码照相机上.游戏机上(一般是掌机)等都小型电子设备都频繁的使用到这种统称为SD的产品,比如TF卡和SD卡( ...

  5. python中的JSON(1)

    很多程序都要求用户输入某种信息, 例如:   让用户存储游戏首选项或提供要可视化的数据,程序把用户的信息存储在列表和字典等数据结构中, 用户关闭程序时,我们几乎总要保存他们提供的信息: 如何保存-- ...

  6. java笔记--问题总结

    1. 垃圾回收算法 标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和 ...

  7. 悟空模式-java-单例模式

    [那座山,正当顶上,有一块仙石.其石有三丈六尺五寸高,有二丈四尺围圆.三丈六尺五寸高,按周天三百六十五度:二丈四尺围圆,按政历二十四气.上有九窍八孔,按九宫八卦.四面更无树木遮阴,左右倒有芝兰相衬.盖 ...

  8. TF卡和SD卡的区别

    小型存储设备凭借低廉的价格.多样化的品种.实用等特性大量充斥在大家身边,比如智能手机手机上.数码照相机上.游戏机上(一般是掌机)等都小型电子设备都频繁的使用到这种统称为SD的产品,比如TF卡和SD卡( ...

  9. Python基础学习总结(八)

    10.文件和异常 1.学习处理文件,让程序快速的分析大量数据,学习处理错误,避免程序在面对意外时崩溃.学习异常,异常是python创建的特殊对象,用于管理程序运行时出现的错误,提高程序的适用性,可用性 ...

随机推荐

  1. 解决本地文件上传时fakepath的问题

    $("input[type='file']").on('change', function () { var oFReader = new FileReader(); var fi ...

  2. Java数据结构和算法(五)——队列

    队列.queue,就是现实生活中的排队. 1.简单队列: public class Queqe { private int array[]; private int front; private in ...

  3. Springboot 生成验证码

    技术:springboot+kaptcha+session   概述 场景介绍 验证码,用于web网站.用户点击验证码图片后,生成验证码.提交后,用户输入验证码和Session验证码,进行校验. 详细 ...

  4. 射频与微波测量之S参数

    转自:https://www.cnblogs.com/lyh523329053/p/9128577.html S参数 S散射也叫散射参数.是微波传输中的一组重要参数.由于我们很难在高频率时测量电流或电 ...

  5. Android recovery支持adb shell

    Android recovery支持adb shell 近期开发过程注意到recovery不支持adb shell.为了便于调试方便,决定添加此功能. 刚開始我们採用的是user版本号系统,进入rec ...

  6. Asp.Net WebApi 跨域设置

    跨越问题主要发生在客户端ajax请求时,为了安全设置,默认webapi是不允许ajax跨越请求的,不过有方法设置让支持跨越,我说说最常见的两种方法 一.jquery jsonp 缺点:JSONP也有局 ...

  7. 光纤网卡、HBA卡和RAID卡的区别(图)

    原文地址:http://wenku.baidu.com/link?url=suuaTXbO_HXeNvuEfi8_RhRAfhQdoZ854lEK4K6LKprgQwwuxA-i3ItwPn7BBBK ...

  8. hdoj:2056

    #include <iostream> #include <iomanip> #include <cstdlib> using namespace std; str ...

  9. 【gulp】gulp-file-include 合并 html 文件

    gulp-file-include 是 gulp 插件,它提供了一个 include 方法让我们可以像后端模板那样把公共部分的页面导入进来. 安装依赖包(包括了 gulp-file-include 和 ...

  10. 分析“HTTP500内部服务器错误”解决方法

    系统中出现的一些故障是用户最不想看到的,有时候遇到一些故障都到折腾个大半天才能够搞定.打开网站出现了HTTP500内部服务器错误的提示,无法法查看错误的详细信息,用户也没办法根据具体的问题来解决,这对 ...