好吧,最近我特么是跟高并发杠上了。。
单例模式想必很很常见,而往往单例模式跟static相关。单例模式的初衷是为了在任何条件下我只得到一个实例,包括类和变量。而往往需要我们用static关键字去修饰达到单例的效果。最近高并发接触得比较多,使用缓存就需要用单例。因为你针对某一个key的缓存只可能定义成“一份”。所以缓存类的实例需要用到单例模式。但是在高并发的条件下,控制不好的话,很容易出问题。下面写个小例子,就能看出是什么问题了……
@Controller public class TestAction { @RequestMapping("/test/context.json") @ResponseBody public void test() { Thread t = Thread.currentThread(); new Thread(new TestThread("count")).start(); try { t.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new TestThread("count")).start(); } } class TestThread implements Runnable{ private String attr; public TestThread(String attr) { this.attr = attr; } @Override public void run() { List<String> list = Test3.getList(attr); list.add("d"); System.out.println(list); System.out.println("==========="); } }
这里用启动两个线程TestThread模拟“并发”。
而我们再模拟使用到单例模式的情形:
public class Test3 { private static List<String> list = new ArrayList<String>(); public static List<String> getList(String attr) { WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext(); //这里用到ServletContext模拟缓存的情况 ServletContext sc = wc.getServletContext(); String count = (String) sc.getAttribute(attr); if(StringUtils.equals("1", count)) { //啥也不做 }else{ sc.setAttribute(attr, "1"); list.add("a"); list.add("b"); list.add("c"); } return list; } }
其中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的方法如下:
public static List<String> getList(String attr) { WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext(); ServletContext sc = wc.getServletContext(); String count = (String) sc.getAttribute(attr); if(StringUtils.equals("1", count)) { //啥也不做 }else{ sc.setAttribute(attr, "1"); list.add("a"); list.add("b"); list.add("c"); } List<String> copyList = new ArrayList<String>(list); return copyList; }
copyList是公共变量的副本,这样,当有N个线程去访问公共变量时,得到的是副本,你之后再对该副本进行任何操作,都不会影响公共变量,从而不影响其他线程对该公共变量的访问,确保其他线程拿到的都是【最初】的公共变量。
同样,访问Url,
打印如下:
[a, b, c, d]
===========
[a, b, c, d]
===========
再次访问:
[a, b, c, d]
===========
[a, b, c, d]
===========
说明:问题解决。
相关推荐
在讨论多线程单例模式及并发访问之前,我们先来了解一些基本概念。 **进程**和**线程**是计算机科学中的两个核心概念,它们之间的关系紧密而复杂。 - **进程**:具有独立功能的程序在某个数据集合上的一次运行活动...
单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在单例模式中,类的构造函数是私有的,防止外部直接创建对象,而是通过静态方法获取该类的唯一实例。单例模式的唯一性通常是在进程范围内,...
在软件设计中,模式是一种解决常见问题的有效方法。"设计模式单例模式和工厂模式综合应用"的主题聚焦于两种常用的设计模式:单例模式和工厂模式,并探讨它们如何协同工作来实现高效、灵活的代码结构。这个主题尤其...
单例模式是软件设计模式中的一种经典模式,它保证了类只有一个实例存在,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。结合工厂模式,...
在软件工程中,设计模式是一种在特定场景下解决常见问题的标准方案,可以被复用并提升代码质量。单例模式是设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式常用于控制共享资源,如...
这种方式能够确保即使在高并发环境下,也能准确地记录每一次访问。 **4. 应用程序的日志处理** 在开发和运维阶段,日志记录对于追踪错误和调试有着不可替代的作用。考虑到日志文件通常需要在多个模块间共享,若...
在C#中,单例模式常用于管理共享资源或控制类的实例化过程,以提高性能、节约系统资源,特别是在整个应用程序生命周期内只需要一个对象的情况下。 首先,实现C#单例模式通常有几种常见方法: 1. 饿汉式(静态常量...
单例模式是软件设计模式中的一种基础且广泛应用的模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,且对象创建成本较高,或者需要共享资源的情况下非常...
单例模式是软件设计模式中的一种经典模式,用于确保一个类只有一个实例,并提供一个全局访问点。在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们...
线程安全的单例模式在多线程环境下尤其重要,因为不正确的实现可能导致多个线程创建多个实例,这违反了单例模式的基本原则。C++11引入了新的特性,如std::mutex和std::call_once,使得实现线程安全的单例模式变得...
单例模式是软件设计模式中的一种经典模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置对象等。下面将详细介绍七种常见的单例模式实现...
在软件工程中,单例模式常用于控制资源的共享,比如数据库连接池、线程池或者日志系统等,这些资源通常需要全局唯一且高效地访问。 在Java中,实现单例模式有多种方式,但最常见的问题是线程安全问题。例如,上述...
需要注意的是,虽然单例模式在很多情况下都很实用,但也有一些潜在的问题,如单例的生命周期与应用程序一致,可能导致资源无法释放,增加内存泄漏的风险。此外,过度依赖单例可能导致代码之间的紧密耦合,不利于测试...
在C++编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在这个特定的场景中,我们讨论的是一个实现了单例模式的日志类,该类专为多线程环境设计,具备日志等级控制、...
在编程领域,设计模式是一种被广泛采用的最佳实践,它提供了在特定情况下解决常见问题的模板。其中,单例模式是一种非常经典且常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在C#...
### 单例模式详解 #### 概述与应用场景 单例模式是一种常用的设计模式,它的主要目的是确保某个类仅有一个实例...通过上述讨论,我们可以看到单例模式在不同场景下的适用性及其潜在问题,并学习到了相应的解决方案。
2. **懒汉单例模式**:延迟到第一次调用`getInstance()`方法时才进行实例化,这种实现方式解决了饿汉模式的内存浪费问题,但在多线程环境下不安全。 ```java public class Singleton { private static Singleton ...