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() {@Overridepublic 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() {@Overridepublic 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的魅力;
轉載于:https://www.cnblogs.com/iCanhua/p/9532396.html
總結
以上是生活随笔為你收集整理的DCL并非单例模式专用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今年的iPhone12便宜吗值得购买吗
- 下一篇: 第1章 计算机系统漫游(深入理解计算机系