单列、多例
单例多例需要搞明白几个问题
1) 什么是单例多例; 2) 如何产生单例多例; 3) 为什么要用单例多例 4) 什么时候用单例,5)什么时候用多例;
什么是单例多例:
所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;
如何产生单例多例:
在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope=“prototype”;
为什么用单例多例:
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存; 之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理; 用单例和多例的标准只有一个: 当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;
何时用单例?何时用多例?
对于struts2来说,action必须用多例,因为action本身含有请求参数的值,即可改变的状态; 而对于STRUTS1来说,action则可用单例,因为请求参数的值是放在actionForm中,而非action中的; 另外要说一下,并不是说service或dao一定是单例,标准同第3点所讲的,就曾见过有的service中也包含了可改变的状态,同时执行方法也依赖该状态,但一样用的单例,这样就会出现隐藏的BUG,而并发的BUG通常很难重现和查找;
解决方案二:
你给你需要频繁访问一个对象,可以用单例,避免创建过多的垃圾
解决方案三:
单例模式可以保证系统中一个类只有一个实例而且该实例和外界通信,解约资源,便于维护
延伸联想到的知识
使用spring产生的疑惑
-
在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,我大部分人都曾经是这么认为的:
DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO 对象就不能是单实例的了。
上述观点对了一半。对的是“每个DAO都要包含一个不同的Connection对象实例”这句话,错的是“DAO对象就不能是单实例”。
-
其实Spring在实现Service和DAO对象时,使用了ThreadLocal这个类,这个是一切的核心! 如果你不知道什么事ThreadLocal,请看《 深入研究java.lang.ThreadLocal类》 。请放心,这个类很简单的。
疑惑答疑
-
ThreadLocal
- 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
- 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
要弄明白这一切,又得明白事务管理在Spring中是怎么工作的,所以本文就对Spring中多线程、事务的问题进行解析。Spring使用ThreadLocal解决线程安全问题:
Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。 -
参考下面代码,这个是《Spring3.x企业应用开发实战中的例子》
public class SqlConnection { //①使用ThreadLocal保存Connection变量 privatestatic ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>(); publicstatic Connection getConnection() { // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, // 并将其保存到线程本地变量中。 if (connThreadLocal.get() == null) { Connection conn = getConnection(); connThreadLocal.set(conn); return conn; } else { return connThreadLocal.get(); // ③直接返回线程本地变量 } } public voidaddTopic() { // ④从ThreadLocal中获取线程对应的Connection try { Statement stat = getConnection().createStatement(); } catch (SQLException e) { e.printStackTrace(); } }
这个是例子展示了不同线程使用TopicDao时如何使得每个线程都获得不同的Connection实例副本,同时保持TopicDao本身是单实例。事务管理器:事务管理器用于管理各个事务方法,它产生一个事务管理上下文。
我们知道数据库连接Connection在不同线程中是不能共享的,事务管理器为不同的事务线程利用ThreadLocal类提供独立的Connection副本。事实上,它将Service和Dao中所有线程不安全的变量都提取出来单独放在一个地方,并用ThreadLocal替换。而多线程可以共享的部分则以单实例方式存在。
总结
- Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。
- 当要频繁访问某个类,且这个类中没有要变化的成员,我们通常会使用单例模式。当类中存在变化的成员我可以选择多例或者单例+ThreadLocal
- 单例可以避免重复创建对象时造成CPU损耗和GC次数过多,可以保证系统中一个类只有一个实例而且该实例和外界通信,解约资源,便于维护
参考文章:
什么时候用单例,什么时候用多例?
spring单例,为什么service和dao确能保证线程安全 但是这个文章有点内容和标题不符,故摘录了部分内容