Spring框架中的单例Bean是线程安全的吗?如何保证?

         在我们的spring框架中,我们可以使用scope来设置bean的类型。当scope写为singleton时就是单例模式,写为prototype时就是多例模式。

<bean id="test" class="com.jz.Test" scope="singleton" />  
<bean id="test" class="com.jz.Test" scope="prototype" /> 

(scope的值还包括了request、session 等,这边使用这两作为介绍)

单例Bean与多例Bean

        补充一个概念:

  • 有状态的bean就是有数据存储功能。
  • 无状态的bean就是不会保存数据。

        singleton表示该bean全局只有一个实例,Spring中bean的scope默认也是singleton。

        prototype表示该bean在每次被注入的时候,都要重新创建一个实例。

        如果有状态的Bean配置为singleton,便会出现以下情况:用户a想要修改有状态bean中保存的值,但是这时用户b也在修改bean中保存的值,b完成修改后a提交他修改好的值。此时b的操作就被a完全覆盖了。

        如果使用的prototype的方式,每次注入都产生一个实例。就不存在数据共享的情况,也就不存在线程安全的问题。

        Spring中的单例与设计模式里面的单例略有不同,设计模式的单例是在整个应用中只有一个实例,而Spring中的单例是在一个IOC容器中就只有一个实例。

        总结,对于有状态的bean我们要采用prototype的方式以保证线程安全,对于无状态的bean我们可以采取singleton的方式。

在Spring中如何保证成员变量线程安全

        Spring作为一个IOC容器,帮助我们管理了许许多多的bean。但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码

        我们可以使用treadlocal的方式来解决线程安全的问题:

  •  每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
  • 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦

下面我们创建一个有状态的bean: 

public class TopicDao {
	//①一个非线程安全的变量
  private Connection conn;
	//②引用非线程安全变量
  public void addTopic() {
		Statement stat = conn.createStatement();
  }
}

        当我们需要多个线程访问这个成员变量的时候可以使用threadlocl对其进行改造,使每个线程获得都访问他们自己的成员变量。

public class TopicDao {  
    //①使用ThreadLocal保存Connection变量  
    private static ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>();  
    public static Connection getConnection() {  
       // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  
       // 并将其保存到线程本地变量中。  
       if (connThreadLocal.get() == null) {  
           Connection conn = getConnection();  
           connThreadLocal.set(conn);  
           return conn;  
       }
       // ③直接返回线程本地变量
       return connThreadLocal.get();  
    }

    public void addTopic() {  
       // ④从ThreadLocal中获取线程对应的Connection  
       try {
           Statement stat = getConnection().createStatement();  
       } catch (SQLException e) {  
           e.printStackTrace();  
       }
    }
}

        不同的线程在使用TopicDao时,先判断connThreadLocal是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

猜你喜欢

转载自blog.csdn.net/m0_58366209/article/details/127900180