DCL并非单例模式专用
我相信大家都很熟悉DCL,對(duì)于缺少實(shí)踐經(jīng)驗(yàn)的程序開發(fā)人員來說,DCL的學(xué)習(xí)基本限制在單例模式,但我發(fā)現(xiàn)在高并發(fā)場(chǎng)景中會(huì)經(jīng)常遇到需要用到DCL的場(chǎng)景,但并非用做單例模式,其實(shí)DCL的核心思想和CopyOnWrite很相似,就是在需要的時(shí)候才加鎖;為了說明這個(gè)觀點(diǎn),我先把單例的經(jīng)典代碼防止如下:
先說明幾個(gè)關(guān)鍵詞:
volatile:保證線程的可見性,有序性;這兩點(diǎn)非常重要,可見性讓線程可以馬上獲釋主存變化,二有序性避免指令重排序出現(xiàn)問題;
public class Singleton {//通過volatile關(guān)鍵字來確保安全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;} }大家可以知道,這段代碼是沒有性能瓶頸的線程安全(當(dāng)然,用了volatile是有一定的性能影響,但起碼不需要競(jìng)爭(zhēng)鎖);這代碼只會(huì)在需要的時(shí)候才加鎖,這就是DCL的需要時(shí)加鎖的特性,由第一個(gè)檢查check保證(也就是if (singleton == null));
但DCL的需要時(shí)才加鎖的魅力不僅僅如此場(chǎng)景而已,我們看一個(gè)需求:一個(gè)不要求實(shí)時(shí)性的更新,所有線程公用一個(gè)資源,而且只有滿足某個(gè)條件的時(shí)候才更新,那么多線程需要訪問緩存時(shí),是否需要加鎖呢?不需要的,看如下代碼:
?
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){ // 爭(zhēng)奪更新權(quán)JSONArray temp=cache;synchronized (updateLock){if(temp==cache&&cache.contains(task)){cache = new JSONArray(Collections.synchronizedList(new LinkedList<>()));}else {return 1;}} // 擁有更新權(quán)的繼續(xù)更新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("=========================完成一次批量存儲(chǔ),成功Flush:"+temp.size()); }} catch (Exception e) {System.out.println("更新丟失,策略補(bǔ)救");e.printStackTrace();}}return 1;}這樣保證了性能,也做到了緩存的線程安全;這就是單例的厲害;我在項(xiàng)目中經(jīng)常遇到該類場(chǎng)景,下面給出一個(gè)任務(wù)計(jì)時(shí)器的代碼:
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);}/*** 計(jì)算任務(wù)* @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("計(jì)數(shù)器失效");}double endTime =System.currentTimeMillis();double endCount = taskInfo.get(taskId).get();double percent =(endCount-startCount)/((endTime - startTime)/1000); // System.out.println("目前總成功爬取速率:==========="+percent+"=======目前處理總數(shù)========:"+allCount);System.out.println("目前"+taskId+"成功爬取速率:==========="+percent+"=======目前"+taskId+"處理總數(shù)========:"+endCount);}}});countor.start();}}}/*** 計(jì)算所有任務(wù)* @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("計(jì)數(shù)器失效");}double endTime =System.currentTimeMillis();double endCount = allCount.get();double percent =(endCount-startCount)/((endTime - startTime)/1000);System.out.println("目前總成功爬取速率:==========="+percent+"=======目前處理總數(shù)========:"+allCount); // System.out.println("目前"+taskId+"成功爬取速率:==========="+percent+"=======目前"+taskId+"處理總數(shù)========:"+allCount); }}});countor.start();}}}} }?
同樣的,線程安全的雙重檢測(cè),這就是DCL的魅力;
轉(zhuǎn)載于:https://www.cnblogs.com/iCanhua/p/9532396.html
總結(jié)
以上是生活随笔為你收集整理的DCL并非单例模式专用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今年的iPhone12便宜吗值得购买吗
- 下一篇: what??|诞生才一年的BCH竟面临硬