我相信大家都很熟悉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. MySQL优化之like关键字

    1.%号不放最左边 无法使用索引了,开头是不确定的,MySQL也无法进行优化了,只能扫描表了. 2.使用覆盖索引 如果业务需要%就放开头我们也没办法,一般情况需求都是这样的,毕竟优化还是为业务服务的. ...

  2. Android开发中常见的设计模式 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. ImportError: No module named _tkinter on macos

    MAC OS 10.11.6 lMacBook-Pro:~ xiaomilbq$ python Python 2.7.14 (default, Sep 22 2017, 00:05:22) [GCC ...

  4. MySQL中的isnull、ifnull和nullif函数用法

    isnull(expr) 如expr为null,那么isnull()的返回值为1,否则返回值为0. mysql>select isnull(1+1); ->0 mysql>selec ...

  5. [Python设计模式] 第27章 正则表达式——解释器模式

    github地址:https://github.com/cheesezh/python_design_patterns 解释器模式 解释器模式,给定一个语言,定一个它的文法的一种表示,并定一个一个解释 ...

  6. NOIP2011普及组 数字反转

    题目OJ链接: http://codevs.cn/problem/1130/ https://www.luogu.org/problemnew/show/P1307 2011年NOIP全国联赛普及组 ...

  7. STM32F105 PA9/OTG_FS_VBUS Issues

    https://www.cnblogs.com/shangdawei/p/3264724.html F105 DFU模式下PA9引脚用来检测USB线缆,若电平在2.7~5v则认为插入usb设备(检测到 ...

  8. Spark 精品文章转载(目录)

    学习 Spark 中,别人整理不错的文章,转载至本博客,方便自己学习,并按照不同的模块归档整理.每个文章作者能力不同,写文章所处的时间点不同,可能会略有差异,在阅读的同时,注意当时的文章的内容是否已经 ...

  9. Docker的学习

    学习地址:http://blog.51cto.com/lizhenliang  和 他的视频 一  Docker 的介绍和安装 二 镜像管理 三 容器管理 四 管理应用程序数据 五 使用Docker知 ...

  10. Android设备真实DPI与系统标示DPI——ldpi/mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi

    1.设备真实DPI与系统标示DPI 2.drawable允许的标示DPI值         drawable文件的合法名称如下: 3.如何验证         Demo如下,建立不同dpi的drawa ...