zoukankan      html  css  js  c++  java
  • 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的魅力;

  • 相关阅读:
    手工解析.NET完全限定类型名称
    用Lambda表达式进行函数式编程(续):用C#实现Y组合子
    VS2008亮点:用Lambda表达式进行函数式编程
    用GPU通用并行计算绘制曼德勃罗特集图形 上篇
    Expression Tree上手指南 (一)
    用GPU通用并行计算绘制曼德勃罗特集图形 下篇
    .NET 4.0中的泛型协变和反变
    开发者眼里的WindowsPhone8的帐
    Win8 Metro App里玩XNA:框架问题解决方案
    Cocos2dx for WindowsPhone:一个按钮走天下
  • 原文地址:https://www.cnblogs.com/iCanhua/p/9532396.html
Copyright © 2011-2022 走看看