• 单例模式在高并发情形下造成的访问覆盖问题


    好吧,最近我特么是跟高并发杠上了。。

    单例模式想必很很常见,而往往单例模式跟static相关。单例模式的初衷是为了在任何条件下我只得到一个实例,包括类和变量。而往往需要我们用static关键字去修饰达到单例的效果。最近高并发接触得比较多,使用缓存就需要用单例。因为你针对某一个key的缓存只可能定义成“一份”。所以缓存类的实例需要用到单例模式。但是在高并发的条件下,控制不好的话,很容易出问题。下面写个小例子,就能看出是什么问题了……

    1.  
      @Controller
    2.  
      public class TestAction {
    3.  
       
    4.  
      @RequestMapping("/test/context.json")
    5.  
      @ResponseBody
    6.  
      public void test() {
    7.  
       
    8.  
      Thread t = Thread.currentThread();
    9.  
       
    10.  
      new Thread(new TestThread("count")).start();
    11.  
       
    12.  
      try {
    13.  
      t.sleep(10000);
    14.  
      } catch (InterruptedException e) {
    15.  
      e.printStackTrace();
    16.  
      }
    17.  
       
    18.  
      new Thread(new TestThread("count")).start();
    19.  
       
    20.  
      }
    21.  
      }
    22.  
       
    23.  
      class TestThread implements Runnable{
    24.  
       
    25.  
      private String attr;
    26.  
       
    27.  
      public TestThread(String attr) {
    28.  
      this.attr = attr;
    29.  
      }
    30.  
       
    31.  
       
    32.  
      @Override
    33.  
      public void run() {
    34.  
      List<String> list = Test3.getList(attr);
    35.  
      list.add("d");
    36.  
      System.out.println(list);
    37.  
      System.out.println("===========");
    38.  
      }
    39.  
       
    40.  
      }

     这里用启动两个线程TestThread模拟“并发”。

    而我们再模拟使用到单例模式的情形:

    1.  
      public class Test3 {
    2.  
       
    3.  
      private static List<String> list = new ArrayList<String>();
    4.  
       
    5.  
      public static List<String> getList(String attr) {
    6.  
      WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext();
    7.  
      //这里用到ServletContext模拟缓存的情况
    8.  
      ServletContext sc = wc.getServletContext();
    9.  
      String count = (String) sc.getAttribute(attr);
    10.  
      if(StringUtils.equals("1", count)) {
    11.  
      //啥也不做
    12.  
      }else{
    13.  
      sc.setAttribute(attr, "1");
    14.  
      list.add("a");
    15.  
      list.add("b");
    16.  
      list.add("c");
    17.  
      }
    18.  
       
    19.  
      return list;
    20.  
      }
    21.  
      }

     其中list是static的全局变量。这里用ServletContext的特性模拟了缓存的情况。

    看看TestAction中定义的线程TestThread,该线程被启动了2次,(模拟并发),并且2次都是传入同一个参数(模拟相同条件)"count"。

    浏览器输入TestAction注解的Url,可发现控制台打印如下:

    [a, b, c, d]

    ===========

    [a, b, c, d, d]

    ===========

    再次输入该Url,打印如下:

    [a, b, c, d, d, d]

    ===========

    [a, b, c, d, d, d, d]

    ===========

    问题已很明显了,线程第一次执行时,集合本来为[a.b.c]被它修改(add("d"))之后,集合被覆盖为[a,b,c,d]了;同理,第二次输入Url之后,集合又被线程第二次执行时覆盖为[a,b,c,d,d]了·,所以此次在进行add("d")操作之后,集合被覆盖为[a,b,c,d,d,d]啦,以此类推……

    其实这种问题是比较容易被忽视的,并发条件下,你对一个“公共”的变量(一般是由static修饰),常见场景如缓存的操作(这里是add("d"))修改,会不断更新【最初】的变量值,【新】的线程再次访问时,得到的已经不是【最初】的值了。这显然是不对的,我们需要做到对一个公共变量进行多线程访问时,线程与线程之间的访问不彼此影响,即:线程不会修改公共的变量值,不影响其他线程的访问。

    注意:需要注意这种情况只涉及到线程需要对拿到的公共变量修改时,纯读取的话,没必要注意这个问题。

    如何解决呢?我们只需拷贝一个公共变量的“副本”,即可达到想要的效果:

    改变Test3的方法如下:

    1.  
      public static List<String> getList(String attr) {
    2.  
      WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext();
    3.  
      ServletContext sc = wc.getServletContext();
    4.  
      String count = (String) sc.getAttribute(attr);
    5.  
      if(StringUtils.equals("1", count)) {
    6.  
      //啥也不做
    7.  
      }else{
    8.  
      sc.setAttribute(attr, "1");
    9.  
      list.add("a");
    10.  
      list.add("b");
    11.  
      list.add("c");
    12.  
      }
    13.  
       
    14.  
      List<String> copyList = new ArrayList<String>(list);
    15.  
      return copyList;
    16.  
      }

     copyList是公共变量的副本,这样,当有N个线程去访问公共变量时,得到的是副本,你之后再对该副本进行任何操作,都不会影响公共变量,从而不影响其他线程对该公共变量的访问,确保其他线程拿到的都是【最初】的公共变量。

    同样,访问Url,

    打印如下:

    [a, b, c, d]

    ===========

    [a, b, c, d]

    ===========

    再次访问:

    [a, b, c, d]

    ===========

    [a, b, c, d]

    ===========

    说明:问题解决。

    --------------------- 本文来自 qq_18875541 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/qq_18875541/article/details/69391730?utm_source=copy 

  • 相关阅读:
    Git的安装
    报错Invalid character found in method name. HTTP method names must be tokens|the HTTP protoco
    Spring Cloud(二)—— Eureka注册与发现
    spring-boot swagger2 设置全局token,说明页面接口无法带入token
    c# 结构体中包含结构体数组的使用
    百度地图api热力图时报错Cannot read property 'y' of undefined
    springboot使用freemaker导出word文档
    c# 同时运行两个相同的程序
    idea maven的pom文件已导入依赖,但是无法引入该包中class
    bootstrap Table 导出时时间格式显示秒 科学计数法显示
  • 原文地址:https://www.cnblogs.com/ZenoLiang/p/9757240.html
Copyright © 2020-2023  润新知