DCL并非单例模式专用
Posted icanhua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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并非单例模式专用的主要内容,如果未能解决你的问题,请参考以下文章