我相信大家都很熟悉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. nvidia-docker2配置与NVIDIA驱动安装

    要运行高版本的GPU版TensorFlow,需要更新宿主机的显卡驱动(本文以NVIDIA390为例) 一.更新驱动 禁用nouveau驱动: 添加/etc/modprobe.d/blacklist.c ...

  2. 对图片进行透明化处理-使用java程序

    因需要将一张白色背景图片处理为透明色,因此上网上搜了搜处理方案,可以通过ps,和美图秀秀,但是我电脑上并没有这两个软件,下载安装太耗时.从网上搜了搜发现原来可以使用java代码进行处理,代码如下: i ...

  3. DockerSwarm获取Token与常用命令

    一.Token相关 Join tokens是允许一个节点加入集群的密钥.有两种可用的不同的join tokens,一个是用作worker角色,另一个是用作manager角色.在执行swarm join ...

  4. android自己定义控件之飞入飞出控件

    近期呢,本人辞职了.在找工作期间.不幸碰到了这个求职淡季,另外还是大学生毕业求职的高峰期,简历发了无数份却都石沉大海.宝宝心里那是一个苦啊! 翻着过去的代码,本人偶然找到了一个有意思的控件.那时本人还 ...

  5. [Java] Windows/Linux路径不同时,统一war的最简办法

    作者: zyl910 一.缘由 在项目开发时,因为运行环境的不同,导致有时得分别为不同的环境,切换配置参数打不同war包.但手工切换配置文件的话,不仅费时费力,而且容易出错. 有些打包工具支持配置切换 ...

  6. Delphi提取PDF文本

    生成PDF的控件很多,但解析的不是太多,pdf Toolkit可以,但测试的第一个复杂的pdf就报告错误,并且汉字乱码,可能使用的版本或使用方法不对. 想起之前使用java调用的Apache名下的pd ...

  7. Effective Java 第三版——80. EXECUTORS, TASKS, STREAMS 优于线程

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  8. 让我头疼一下午的Excel合并单元格

    Excel导出常见问题 excel导出其实不算什么难事 在网上copy下模板代码,填充自己的业务数据,提供一个http接口基本就可以得到你要导出的数据了. 但是,凡事都有例外,截止今天,excel导出 ...

  9. UltraVNC 简体中文版 1.2.2.1

    1.专门针对WinXP进行编译,同时适用XP之后的Windows版本(XP/Vista/8.1/10/2003/2008/2012): 2.配置低的计算机,Win8.1之前的系统,需要安装Mirror ...

  10. 【转载】史上最全:TensorFlow 好玩的技术、应用和你不知道的黑科技

    [导读]TensorFlow 在 2015 年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习.深度学习项目中最受欢迎的框架之一.自发布以来,TensorFlow 不断在完善并增加 ...