DCL并非单例模式专用
我相信大家都很熟悉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并非单例模式专用的更多相关文章
- DCL之单例模式
所谓的DCL 就是 Double Check Lock,即双重锁定检查,在了解DCL在单例模式中如何应用之前,我们先了解一下单例模式.单例模式通常分为"饿汉"和"懒汉&q ...
- Android设计模式——单例模式
1.单例模式就是确保一个类,只有一个实例化对象,而且自行实例化并向整个系统提供这个实例. 2.使用场景: 确保某个类,有且只有一个对象,避免产生对个对象,消耗过多的资源. 2.实现单例模式的重要点: ...
- volatile 关键字精讲
1.错误案例 通过一个案例引出volatile关键字,例如以下代码示例 : 此时没有加volatile关键字两个线程间的通讯就会有问题 public class ThreadsShare { priv ...
- 图文:TF卡和SD卡的区别及什么是TF卡?什么是SD卡
小型存储设备凭借低廉的价格.多样化的品种.实用等特性大量充斥在大家身边,比如智能手机手机上.数码照相机上.游戏机上(一般是掌机)等都小型电子设备都频繁的使用到这种统称为SD的产品,比如TF卡和SD卡( ...
- python中的JSON(1)
很多程序都要求用户输入某种信息, 例如: 让用户存储游戏首选项或提供要可视化的数据,程序把用户的信息存储在列表和字典等数据结构中, 用户关闭程序时,我们几乎总要保存他们提供的信息: 如何保存-- ...
- java笔记--问题总结
1. 垃圾回收算法 标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和 ...
- 悟空模式-java-单例模式
[那座山,正当顶上,有一块仙石.其石有三丈六尺五寸高,有二丈四尺围圆.三丈六尺五寸高,按周天三百六十五度:二丈四尺围圆,按政历二十四气.上有九窍八孔,按九宫八卦.四面更无树木遮阴,左右倒有芝兰相衬.盖 ...
- TF卡和SD卡的区别
小型存储设备凭借低廉的价格.多样化的品种.实用等特性大量充斥在大家身边,比如智能手机手机上.数码照相机上.游戏机上(一般是掌机)等都小型电子设备都频繁的使用到这种统称为SD的产品,比如TF卡和SD卡( ...
- Python基础学习总结(八)
10.文件和异常 1.学习处理文件,让程序快速的分析大量数据,学习处理错误,避免程序在面对意外时崩溃.学习异常,异常是python创建的特殊对象,用于管理程序运行时出现的错误,提高程序的适用性,可用性 ...
随机推荐
- 对于eclipse building workspaces 慢的问题,解决方法
在项目根目录中有个.project文件,将其中的: <buildCommand> <name>org.eclipse.wst.jsdt.core.javascriptValid ...
- idea的debug模式启动运行慢
今天发现idea的debug模式启动运行特别慢,查找原因是因为在多个方法上打了断点导致,关闭断点问题解决. 关闭断点方法为:
- netty中的ChannelHandler和ChannelPipeline
netty中的ChannelHandler和ChannelPipeline ChannelHandler 家族 https://www.w3cschool.cn/essential_netty_in_ ...
- 初识Vulkan【转】
Vulkan是Khronos组织制定的“下一代”开放的图形显示API.是与DirectX12能够匹敌的GPU API标准. Vulkan是基于AMD的Mantle API演化而来,眼下Vulkan 1 ...
- [Python设计模式] 第14章 老板来了——观察者模式
github地址:https://github.com/cheesezh/python_design_patterns 题目 用程序模拟以下情景,在一个办公室里,当老板进门的时候,前台秘书就偷偷通知办 ...
- Git - 生成ssh key步骤以及如何clone所有的远程分支
https://www.cnblogs.com/gongyuhonglou/p/6922721.html 2. 生成ssh key $ ssh-keygen -t rsa -C “邮箱”按3个回车,密 ...
- Android报“android.content.res.Resources$NotFoundException: String resource ID #0x2”错误
Android报“android.content.res.Resources$NotFoundException: String resource ID #0x2”错误 当调用setText()方法时 ...
- Redis 学习之路 (011) - redis 多数据库
一台服务器上都快开启200个redis实例了,看着就崩溃了.这么做无非就是想让不同类型的数据属于不同的应用程序而彼此分开. 那么,redis有没有什么方法使不同的应用程序数据彼此分开同时又存储在相同的 ...
- 离线环境下安装ansible,借助有网环境下pip工具
环境 有网的机器(192.168.19.222):rhe65,python2.7.13,pip9.0.1 离线机器(192.168.19.203):rhe65,python2.6 FTP(192.16 ...
- [k8s]Docker 用户使用 kubectl 命令指南-unkown排错(kubelet端口解析)
参考:https://kubernetes.io/docs/user-guide/kubectl-cheatsheet/ https://k8smeetup.github.io/docs/user-g ...