[设计模式/Java/多线程] 设计模式之单例模式【9】

0 序

  • 此文系对最常见的设计模式------------单例模式的最全总结。

1 概述:单例模式

模式定义

  • 单例模式:
  • 保证1个类有且仅有1个实例,并提供1个访问它的全局访问点。
  • 1个类有且仅有1个实例,并自行实例化向整个系统提供。
    即 为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种通用方法。
  • 如果一个类的实例应该在JVM初始化时被创建出来,应考虑使用: 饿汉式单例模式。

  • 在使用懒汉式 单例模式时,应考虑到: 线程安全性问题。

  • 通常,我们可以让1个全局变量,使得1个对象被访问,但它不能防止你实例化多个对象。

1个最好的办法就是,让类自身负责保存它的唯一实例。

这个类可以保证没有其它实例可以被创建。并且它可以提供1个访问该实例的方法。

模式的组成: 3要素

  • 私有的静态的实例对象 instance
  • 私有的构造函数 Singleton()

保证在该类外部,无法通过new的方式来创建对象实例

  • 公有的、静态的、唯一的访问该实例对象的方法 getInstance()

模式特点

优点

  • 允许可变数目的实例。
  • 可保证1个类仅存在唯一的实例(保证没有其它实例可以被创建)
  • 唯一实例的受控访问:可严格地控制客户访问实例的方式和访问实例的时机。
  • 可保证对单例类的所有实例化得到的都是同一个实例
  • 单例模式在实例化过程中具有一定的伸缩性

类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

  • 节约系统资源,避免对共享资源的多重占用。

当需要频繁创建和销毁的对象时,单例模式无疑可以提高系统的性能。

缺点

  • 单例类的职责过重,在一定程度上违背了"单一职责原则"。
  • 不适用于变化的对象
    如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  • 不易扩展,违背"开闭原则"。
    单例类的扩展有很大的困难,因为:单例模式中没有抽象层。
  • 滥用单例将带来一些负面问题:

例如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;

如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

适用场景

  • 需要频繁实例化 ,然后销毁的对象。
  • 创建对象时 耗时过多或者耗资源过多 ,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。

Eg:日志文件,应用配置等

  • 控制资源的情况下,方便资源之间的互相通信。

Eg:线程池、数据库连接池等

案例实践

案例:简单的单例实现

Singleton类,定义1个getInstance()操作,允许客户端访问它的唯一实例。
getInstance是静态方法,主要负责创建自己类的唯一实例。

  • Singleton
java 复制代码
public class Singleton {
    private  static Singleton instance;//即 初始为null
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • Client
java 复制代码
public class Client {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
//true (变量的对象引用地址一致,说明二者是指向同一实例化对象)
    }
}

案例:线程池对象/连接池对象的创建

  • 为了程序的高效率地使用多线程并发 ,然而是循环调用 ,可能导致创建线程数过多,考虑采用线程池 管理,这时候创建线程池仍然是处于循环调用中,也可能导致多个线程池,这时候就考虑使用单例模式
java 复制代码
/**
 * 多线程下,线程安全的一种依赖于静态内部类实现的单例模式
 */
public class ThreadPoolFactoryUtil {//单例类
    private ExecutorService executorService;
    //在构造函数中创建线程池
    private ThreadPoolFactoryUtil(){//私有的构造器
    //获取系统处理器个数,作为线程池数量
        int nThreads=Runtime.getRuntime().availableProcessors();
        executorService = Executors.newFixedThreadPool(nThreads);
    }
    //获取本类对象
    public static ThreadPoolFactoryUtil getThreadPoolFactoryUtil(){ //公有的、静态的、唯一访问实例对象的方法
        return SingletonContainer.util;
    }
    /**
     * 定义1个静态内部类,内部定义静态成员创建外部类实例
     * 类级的内部类,即 静态的成员式内部类
     * 该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到才会装载,从而实现了【延迟加载】
     */
    private static class SingletonContainer{
        //静态初始化器,由JVM来保证线程安全
        private static ThreadPoolFactoryUtil util = new ThreadPoolFactoryUtil();
    }
    public ExecutorService getExecutorService(){ //ThreadPoolFactoryUtil的实例化对象的实例方法
        return executorService;
    }
}

懒汉式的单例模式(延迟加载)

定义

  • 应用刚启动的时候,并不创建实例;
  • 当外部调用该类的实例或者该类实例方法的时候,才创建该类的实例。(时间换空间)

特点

优点 缺点
实例在被使用的时候才被创建,可节省系统资源; 体现了延迟加载的思想。 由于系统刚启动,且未被外部调用时,实例没有创建; 若同一时间有多个线程同时调用 LazySingleton.getLazyInstance() 方法很有可能会产生多个实例 (即 多线程场景 下,单例失效故障
  • 重要结论:
  • 传统的懒汉式单例是【非线程安全】的

案例实践

java 复制代码
public class Singleton {
    private static Singleton instance = null; //【重点】初始时:私有属性 instance 为 null,不实例化(new)对象

    private Singleton(){}

    public static Singleton getInstance(){
        if(instance == null){ //非线程安全的一个显著原因是:可能会有多个线程同时进入 if (instance == null) { ... } 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是【非线程安全】的。
            instance = new Singleton();
        }
        return instance;
    }
}

引申问题:多线程下,懒汉式单例模式的缺陷问题

问题描述

  • 多线程的程序中,多个线程同时访问Singleton类,调用getInstance()方法,会有造成创建多个实例的可能。

问题复现:非线程安全

LazySingleton

java 复制代码
public class LazySingleton {
    private static int count = 0;
    private  static LazySingleton lazyInstance=null; //初始时:私有属性instance为null,不实例化(new)对象

    //↓为了易于模拟多线程下,懒汉式出现的问题:在创建实例的构造函数里,使当前线程暂停了50毫秒
    private LazySingleton(){
        try{
            Thread.sleep(50);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("生成LazySingleton实例一次!"+(++count));
    }
    public static LazySingleton getLazyInstance(){
        if(lazyInstance ==null){ 
            lazyInstance = new LazySingleton();
        }
        return lazyInstance;
    }
}

Client : 制造多个线程,同时创建单例对象

java 复制代码
public class Client {
    @Test
    public void lazySingletonTest() {
        for (int i = 0; i < 10; i++) {//创建10个线程调用
            new Thread() {
                @Override
                public void run() {
                    LazySingleton.getLazyInstance();
                }
            }.start();//end Thread
        }//end for loop
    }//end method
}//end class

out:

  • 单例模式失效,构造器创建/实例化了多个单例类的不同对象!

由此可见,懒汉式的单例模式多线程的并发场景下,会出故障。

问题分析

【分析】

  • 多个线程同时访问上面的懒汉式单例,
  • 现在有两个线程A和B同时访问LazySingleton.getLazyInstance()方法。
  • 假设A先得到CPU的时间切片,A执行到if(lazyInstancenull)时,由于lazyInstance之前并没有实例化,所以lazyInstancenull为true,在还没有执行实例创建的时候此时CPU将执行时间分给了线程B,线程B执行到if(lazyInstancenull)时,由于lazyInstance之前并没有实例化,所以lazyInstancenull为true,线程B继续往下执行实例的创建过程,线程B创建完实例之后,返回。
  • 此时,CPU时间切片 分给线程A,线程A接着开始执行实例的创建,实例创建完之后便返回。
  • 由此,看线程A和线程B分别创建了1个实例(存在2个实例了),这就导致了单例的失效

解决办法:怎么修改传统的懒汉式单例,使其线程变得安全?多线程并发环境下,如何利用Java特性实现线程安全的懒汉式单例?

方案1:(同步方法) public static synchronized LazySingleton getLazyInstance()

  • 可在getLazyInstance()方法上加上synchronized使其同步。
java 复制代码
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized LazySingleton getLazyInstance(){  ... }

但是这样一来,会降低整个访问的速度,而且每次都要判断。

方案2:(同步代码块) 双重检查加锁/双重锁定

  • 那么,有没有更好的方式来实现呢?可以考虑使用"双重检查加锁"。

"双重检查加锁 "的方式来实现,就可以既实现线程安全 ,又能够使性能 不受到很大的影响。

具体代码如下:

java 复制代码
public class LazySingleton {
    private static int count = 0;
    private  static LazySingleton lazyInstance=null; //初始时:私有属性instance为null,不实例化(new)对象
    //↓为了易于模拟多线程下,懒汉式出现的问题:在创建实例的构造函数里,使当前线程暂停了50毫秒
    private LazySingleton(){
        try{
            Thread.sleep(50);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("生成LazySingleton实例一次!"+(++count));
    }
    //双重检查加锁 解决懒汉式单例模式的多线程不安全问题
    public static LazySingleton getLazyInstance(){
        if(lazyInstance==null){//先检查实例是否存在?(第1次) 若不存在时:才进入下面的同步块(即 第一重加锁处理)
            //同步块,线程安全地创建实例
            synchronized (LazySingleton.class){ //【重点】 使用 synchronized 块,临界资源的同步互斥访问
                //再次检查实例是否存在?(第2次)如果不存在才真正地创建实例
                if(lazyInstance==null){
                    lazyInstance=new LazySingleton();
                }
            }
        }
        return lazyInstance;
    }
}

out:

``java

生成 LazySingleton 实例1次!

复制代码
#### 方案3:静态内部类(实现 延迟加载和线程安全)
+ <span style="color:red">静态内部类 = 静态的成员式内部类</span>
+ 静态内部类无需依赖于外部类,它可独立于外部对象而存在。
+ 静态内部类,多个外部类的对象可【共享】同一个内部类的对象。
+ 使用**静态内部类**的【好处】: 加强了代码的封装性及提高了代码的可读性。

+ 普通内部类不能声明`static`的方法和变量 
> 即 `final static` 修饰的属性还是可以的,而静态内部类形似外部类,无任何限制
> 可直接被用外部类名+内部类名获得

+ 具体代码:
```java
public class GracefulSingleton {//单例类
    private GracefulSingleton() { //私有的构造方法
        System.out.println("创建GracefulSingleton实例一次!");
    }
    public static GracefulSingleton getInstance() {//公有的、静态的、唯一的访问实例对象的方法
        return SingletonHolder.instance;
    }
    /**
     * 定义1个静态内部类,内部定义静态成员创建外部类实例
     * 类级的内部类,即 静态的成员式内部类
     * 该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到才会装载,从而实现【延迟加载】
     */
    private static class SingletonHolder {//【重点】
        //静态初始化器,由JVM来保证线程安全
        private static GracefulSingleton instance = new GracefulSingleton();
    }
}

方案4:(单例模式的变种)单例模式 与 ThreadLocal

  • 借助于 ThreadLocal,我们可以实现双重检查模式的变体 ------------核心思想:将临界资源 instance线程私有化(局部化)

具体到本例就是: 将双重检测 的第一层检测条件 if (instance == null) 转换为线程局部范围内的操作,对应的代码清单如下:

java 复制代码
public class Singleton {
    // ThreadLocal 线程局部变量,将单例instance线程私有化
    private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>(); //【重点】
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
        if (threadlocal.get() == null) {
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查:该单例是否被创建
                    instance = new Singleton();
                }
            }
            threadlocal.set(instance); // 将单例放入ThreadLocal中
        }
        return threadlocal.get();
    }
}

out

log 复制代码
Output(完全一致): 
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155
        1028355155

饿汉式的单例模式(立即加载)

  • 开发中,相较于懒汉式单例模式,饿汉式使用更多。

定义

  • 应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。(空间换时间

特点

  • 优点
  • 写法简单;
  • 在多线程下,也能保证单例实例的唯一性;
  • 不用同步;
  • 运行效率高
  • 缺点
  • 一直未被调用时,可能存在无意义地消耗系统资源 (资源浪费)

案例实践

java 复制代码
public final class Singleton { //final阻止派生类,派生可能会增加实例
//在第1次引用类的任何成员时,就创建单例类实例。
    private  static Singleton instance=new Singleton(); //【重点】初始时:私有属性instance就实例化(new)对象
    private Singleton(){}
    public static Singleton getInstance(){//直接返回实例
        return instance;
    }
}

辨析对比:饿汉式单例 VS 懒汉式单例

总结

  • 速度/性能反应时间 角度来讲,饿汉式 (又称立即加载)要好一些;
  • 资源利用效率 上说,懒汉式 (又称延迟加载)要好一些。

案例与源码分析 | Runtime类 : 饿汉式单例

java 复制代码
public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
     private Runtime() {}
}
  • Runtime类封装了Java运行时的环境
  • 每一个java程序 实际上都是启动了一个JVM进程
  • 那么,每个JVM进程 都是对应这一个Runtime实例,此实例是由JVM为其实例化的。
  • 每个 Java 应用程序 都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
  • 由于Java是单进程的。

所以,在一个JVM中,Runtime的实例应该只有一个。

所以,应该使用单例来实现。

  • 饿汉式单例模式
  • Runtime 类第一次被classloader加载的时候,实例就被创建出来了。
  • 一般不能实例化一个 Runtime 对象,应用程序也不能创建自己的 Runtime 类实例;
  • 但可以通过 getRuntime 方法获取当前 Runtime 运行时对象的引用。

案例与源码分析 | JDBC/DriverManager#getConnection(...): 懒汉式单例(双重检查加锁)

  • 回忆一下: 基于 jdbc 获取连接
java 复制代码
// 注册 JDBC 驱动
Class clz =  Class.forName(JDBC_DRIVER);
// 打开链接
conn = DriverManager.getConnection(DB_URL,USER,PASS);
  • DriverManager
java 复制代码
@CallerSensitive
public static Connection getConnection(String url, String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();
    if (user != null) { 
        info.put("user", user); 
    }
    if (password != null) {
        info.put("password", password); 
    }
    return (getConnection(url, info, Reflection.getCallerClass()));
}

//  Worker method called by the public getConnection() methods.
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) { throw new SQLException("The url cannot be null", "08001"); }
    println("DriverManager.getConnection(\"" + url + "\")");
    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    
    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }
    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

案例与源码分析 | Integer.valueOf(int i) :懒汉式单例模式(静态内部类)

  • Integer
java 复制代码
public static Integer valueOf(int i) {//懒汉式单例模式
    if (i >= IntegerCache.low && i <= IntegerCache.high)//已实例化?是,返回IntegerCache数组的对应实例
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);//尚未实例化?是,返回新实例对象
}
  • IntegerCache

注: IntegerCache 是一个数组

java 复制代码
private static class IntegerCache {//私有的静态内部类,用
    static final int low = -128;
    static final int high;
    static final Integer cache[]; //待返回的单例(数组)实例对象(//静态内部类下静态成员,由JVM来保证线程安全)
}

案例与源码分析 | java.awt.Toolkit类#getDefaultToolkit() : 懒汉式单例

  • 不需要事先创建好,只要在第一次真正用到的时候再创建就可以了。
  • 因为很多时候并不常用JavaGUI和其中的对象。
  • 如果使用饿汉单例 的话,会影响JVM的启动速度。

Toolkit

java 复制代码
public abstract class Toolkit {
    private static Toolkit toolkit;

    public static synchronized Toolkit getDefaultToolkit() {
        if (toolkit == null) {
            java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    Class<?> cls = null;
                    String nm = System.getProperty("awt.toolkit");
                    try {
                        cls = Class.forName(nm);
                    } catch (ClassNotFoundException e) {
                        ClassLoader cl = ClassLoader.getSystemClassLoader();
                        if (cl != null) {
                            try {
                                cls = cl.loadClass(nm);
                            } catch (final ClassNotFoundException ignored) {
                                throw new AWTError("Toolkit not found: " + nm);
                            }
                        }
                    }
                    try {
                        if (cls != null) {
                            toolkit = (Toolkit)cls.newInstance();
                            if (GraphicsEnvironment.isHeadless()) {
                                toolkit = new HeadlessToolkit(toolkit);
                            }
                        }
                    } catch (final InstantiationException ignored) {
                        throw new AWTError("Could not instantiate Toolkit: " + nm);
                    } catch (final IllegalAccessException ignored) {
                        throw new AWTError("Could not access Toolkit: " + nm);
                    }
                    return null;
                }
            });
            loadAssistiveTechnologies();
        }
        return toolkit;
    }
}

案例与源码分析 | spring 应用的单例多线程问题

spring 应用的对象创建方式

  • Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。

即 如无特殊配置,Spring框架中默认的、大多数的类实例都是单例对象

例如:Controller层的实例、Service层的实例、DAO层的实例。

  • 单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
  • 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法)。此时就要注意了,如果该处理逻辑中有对该单例对象状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题。

同步机制的比较

锁的同步机制 : 多个线程访问共享对象时需排队 (以时间换空间)

  • 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。

这时该变量是多个线程共享 的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

ThreadLocal : 为每一个线程提供一个独立的变量副本 (以空间换时间)

  • ThreadLocal线程同步机制相比有什么优势呢?
  • ThreadLocal线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  • ThreadLocal则从另一个角度来解决多线程的并发访问 。ThreadLocal会为每一个线程提供一个独立的变量副本 ,从而隔离多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象 ,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal
  • 由于ThreadLocal中可以持有任何类型的对象,低版本JDK 所提供的get()返回的是Object对象,需要强制类型转换 。但JDK 5.0通过泛型 很好的解决了这个问题,在一定程度地简化了ThreadLocal的使用
  • 概括起来说,对于多线程资源共享 的问题,同步机制 采用了"以时间换空间 "的方式,而ThreadLocal采用了"以空间换时间"的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Spring使用ThreadLocal解决线程安全问题

  • 在一般情况下,只有无状态的Bean 才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

  • 一般的【Web应用】划分为展现层服务层持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。

在一般情况下,从接收请求返回响应所经过的所有程序调用都同属于一个线程。

  • ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本 解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

是否存在【线程安全】问题的判断思路

  • 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。
  • 如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
  • 或者说:一个类或者程序所提供的接口对于线程 来说是原子操作 或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

线程安全问题 都是由全局变量静态变量引起的。

  • 若每个线程中对全局变量静态变量 只有读操作 ,而无写操作

一般来说,这个全局变量 是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步 ,否则就可能影响线程安全

txt 复制代码
1)【常量】始终是【线程安全】的,因为只存在读操作。 

2)每次调用方法前都新建一个实例是【线程安全】的,因为不会访问共享的资源。

3)【局部变量】是线程安全的。因为每执行一个方法,都会在【独立的空间】创建【局部变量】,它不是【共享的资源】。
局部变量包括方法的参数变量和方法内变量。
  • 有状态就是有数据存储功能。

有状态对象 (Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间,不保留任何状态。

  • 无状态就是一次操作,不保存数据。

无状态对象 (Stateless Bean),就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。

参考文献

Strcut2 的线程安全问题

  • 基于Struts2框架(类比 Java Web 框架: Spring MVC)的应用程序的Java对象,默认 的实现是Prototype模式。

也就是每个请求 都新生成一个Action实例,所以不存在线程安全问题。

  • 需要注意的是,如果由Spring管理action的生命周期,scope要配成prototype作用域

Z FAQ

Q: 与实用类(XxxUtils)的区别?

实用类 单例类
不保存状态 仅提供一些静态方法或静态属性让客户访问 (Eg:Math类) 保存状态
不能用于继承多态 允许有子类继承
一些方法、属性的集合 有着唯一的对象实例

Y 推荐文献

X 参考文献

相关推荐
此木|西贝16 小时前
【设计模式】原型模式
java·设计模式·原型模式
高 朗1 天前
2025高频面试设计模型总结篇
设计模式·面试·职场和发展
Summer_Xu2 天前
模拟 Koa 中间件机制与洋葱模型
前端·设计模式·node.js
云徒川2 天前
【设计模式】原型模式
java·设计模式·原型模式
huang_xiaoen2 天前
java设计模式之桥接模式(重生之我在地府当孟婆)
设计模式·桥接模式
HappyGame022 天前
设计模式-观察者模式
观察者模式·设计模式
渊渟岳2 天前
掌握设计模式--解释器模式
设计模式
牵牛老人2 天前
C++设计模式-责任链模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
c++·设计模式·责任链模式
肥仔哥哥19302 天前
设计模式分类与定义(高软55)
设计模式·软考·高软·设计模式分类