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创建的特殊对象,用于管理程序运行时出现的错误,提高程序的适用性,可用性 ...
随机推荐
- shell编程学习笔记(四):Shell中转义字符的输出
通过echo可以输出字符串,下面看一下怎么输出特殊转义字符,首先我先列出来echo的转义字符: \\ 输入\ \a 输出警告音 \b 退格,即向左删除一个字符 \c 取消输出行末的换行符,和-n选项一 ...
- Linux中查看文件夹占用磁盘大小
一.命令 ./ du -h ./ 查看当前目录占用空间 二.样例
- PL/SQL学习笔记之集合
一:PL/SQL集合 集合是一个有序且存有相同的类型数据的数据结构. PL/SQL提供了三种集合类型: 索引表(关联数组) 嵌套表 数组 二:索引表:一个索引表(也叫关联数组)是一组键 - 值对.每个 ...
- Navicat Win 和 Mac 视图类快捷键对比
Navicat 查询是根据用户需求从数据库提取可读格式的数据,Navicat 提供两个强大的工具与 SQL 查询工作:查询创建工具和查询编辑器,查询创建工具可视觉化地创建查询,查询编辑器可直接编辑查询 ...
- 巧用Openlayers4的Style
原文:https://blog.csdn.net/gisshixisheng/article/details/80149087 概述 非常细化Openlayers4中的StyleFunction,因为 ...
- [k8s] flexvolume workflow
- 【转载】systemctl命令完全指南
Systemctl是一个systemd工具,主要负责控制systemd系统和服务管理器. Systemd是一个系统管理守护进程.工具和库的集合,用于取代System V初始进程.Systemd的功能是 ...
- C语言 · 积分之迷
标题:积分之迷 小明开了个网上商店,卖风铃.共有3个品牌:A,B,C. 为了促销,每件商品都会返固定的积分. 小明开业第一天收到了三笔订单: 第一笔:3个A + 7个B + 1个C,共返积分:315 ...
- Linux 设备树属性在驱动中获取方法
获取设备树中的属性,可以直接当设备树为配置文件,方便,快捷. 一般probe之后,调用的 probe函数的参数, 是一个设备结构体. 拿 spi 为例子 static int wk2xxx_probe ...
- mui 浏览器一样自动缩放
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...