(8)springboot-springbean单例
⽬录
Java单例
在了解spring bean单例之前先温习⼀下java单例模式。
java单例模式确保⼀个类只有⼀个实例,⾃⾏提供这个实例并向整个系统提供这个实例。
特点:
1,⼀个类只能有⼀个实例;
2,⾃⼰创建这个实例;
3,整个系统都要使⽤这个实例。
单例模式,能避免实例重复创建;
单例模式,应⽤于避免存在多个实例引起程序逻辑错误的场合;
单例模式,较节约内存。
有多种模式,先了解其中的两种:
饿汉式(静态常量)
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
优点:这种写法⽐较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始⾄终从未使⽤过这个实例,则会造成内存的浪费。
懒汉式(线程安全,同步⽅法)[不推荐⽤]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
缺点:效率太低了,每个线程在想获得类的实例时候,执⾏getInstance()⽅法都要进⾏同步。⽽其实这个⽅法只执⾏⼀次实例化代码就够了,后⾯的想获得该类实例,直接return就⾏了。⽅法进⾏同步效率太低要改进。
单例与多例、⽆状态与有状态
单例:某个类系统范围内只有⼀个实例
多例:某个类在系统范围内同时有多个实例
⽆状态类:类中没有状态信息,⼀般是⽆成员变量或成员变量的值是不变的。
有状态类:类中有状态信息,⼀般表现成员变量的值可变,在某⼀时该被调⽤⽽改变状态,之后再调⽤时获取其正确的状态。
有状态类的实例 ,⼀般是多例的,⽤于保存多个相同类型的不同状态值。因为只有⼀个实例,单线程重复调⽤情况下可能覆盖实例中之前的成员变量值、多线程下⼀次调⽤也可能覆盖实例中之前的成员变量值,造成状态丢失,就需要多个实例来保存不同的状态。
⽆状态类的实例,⼀般是单例的,因为没有状态,所以单线程下重复调⽤或多线程下调⽤对实例没有影响。
有状态的单例:这种情况是为了全局共享状态,状态的修改需要加锁,保证线程的安全性。
单线程下有状态的单例:线程是顺序执⾏的,在同⼀时该只存在⼀种状态,则可以使⽤单例,⼀般的使⽤⽅式是ThreadLocal.
Spring Bean
Spring单例Bean与单例模式的区别在于它们关联的环境不⼀样,单例模式是指在⼀个JVM进程中仅有⼀个实例,⽽Spring单例是指⼀个Spring Bean容器(ApplicationContext)中仅有⼀个实例。
与此相⽐,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在⼀个JVM进程中,如果有多个Spring容器,即使是单例bean,也⼀定会创建多个实例,代码⽰例如下:
// 第⼀个Spring Bean容器
ApplicationContext context_1 = new FileSystemXmlApplicationContext("classpath:/l");
Person yiifaa_1 = Bean("yiifaa", Person.class);
// 第⼆个Spring Bean容器
ApplicationContext context_2 = new FileSystemXmlApplicationContext("classpath:/l");
Person yiifaa_2 = Bean("yiifaa", Person.class);
// 这⾥绝对不会相等,因为创建了多个实例
System.out.println(yiifaa_1 == yiifaa_2);
Spring框架⾥的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地⽅。
单例模式的意思就是只有⼀个实例。单例模式确保某⼀个类只有⼀个实例,⽽且⾃⾏实例化并向整个系统提供这个实例。这个类称为单例类。
当多⽤户同时请求⼀个服务时,容器会给每⼀个请求分配⼀个线程,这是多个线程会并发执⾏该请求
多对应的业务逻辑(成员⽅法),此时就要注意了,如果该处理逻辑中有对该单列状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同⼀时间只有⼀个线程访问变量。这时该变量是多个线程共享的,使⽤同步机制要求程序慎密地分析什么时候对变量进⾏读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较⼤。
⽽ThreadLocal则从另⼀个⾓度来解决多线程的并发访问。ThreadLocal会为每⼀个线程提供⼀个独⽴的变量副本,从⽽隔离了多个线程对数据的访问冲突。因为每⼀个线程都拥有⾃⼰的变量副本,从⽽也就没有必要对该变量进⾏同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采⽤了“以时间换空间”的⽅式,⽽ThreadLocal采⽤了“以空间换时间”的⽅式。前者仅提供⼀份变量,让不同的线程排队访问,⽽后者为每⼀个线程都提供了⼀份变量,因此可以同时访问⽽互不影响。
Spring使⽤ThreadLocal解决线程安全问题
我们知道在⼀般情况下,只有⽆状态的Bean才可以在多线程环境下共享,在Spring中,绝⼤部分Bean都可以声明为singleton作⽤域。就是因为Spring对⼀些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中⾮线程安全状态采⽤ThreadLocal进⾏处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
spring mvc和boot区别默认情况下,如果不为bean指定任何范围,则这些bean将被视为单例(⽆状态)。只创建⼀个为应⽤程序创建的bean
如果为bean指定Scope=“Prototype”,那么这些bean将被视为原型(有状态的)。只要需要,就会创建⼀个新bean。
⼀般的Web应⽤划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接⼝向上层开放功能调⽤。在⼀般情况下,从接收请求到返回响应所经过的所有程序调⽤都同属于⼀个线程。
ThreadLocal是解决线程安全问题⼀个很好的思路,它通过为每个线程提供⼀个独⽴的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal⽐直接使⽤synchronized同步机制解决线程安全问题更简单,更⽅便,且结果程序拥有更⾼的并发性。
如果你的代码所在的进程中有多个线程在同时运⾏,⽽这些线程可能会同时运⾏这段代码。如果每次运⾏结果和单线程运⾏的结果是⼀样的,⽽且其他的变量的值也和预期的是⼀样的,就是线程安全的。 或者说:⼀个类或者程序所提供的接⼝对于线程来说是原⼦操作或者多个线程之间的切换不会导致该接⼝的执⾏结果存在⼆义性,也就是说我们不⽤考虑同步的问题。 线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则就可能影响线程安全。
下⾯的解释可能会更易懂:
springmvc的controller是singleton的(⾮线程安全的),这也许就是他和struts2的区别吧!和Struts⼀样,Spring的Controller 默认是Singleton的,这意味着每个request过来,系统都会⽤原有的instance去处理,这样导致了两个结果:⼀是我们不⽤每次创建Controller,⼆是减少了对象创建和垃圾收集的时间;由于只有⼀个Controller的instance,当多个线程调⽤它的时候,它⾥⾯的instance 变量就不是线程安全的了,会发⽣窜数据的问题。当然⼤多数情况下,我们根本不需要考虑线程安全的问题,⽐如dao,service等,除⾮在bean中声明了实例变量。因此,我们在使⽤spring mvc 的contrller时,应避免在controller中定义实例变量。
如果控制器是使⽤单例形式,且controller中有⼀个私有的变量a,所有请求到同⼀个controller时,使⽤的a变量是共⽤的,即若是某个请求中修改了这个变量a,则,在别的请求中能够读到这个修改的内容。。
有⼏种解决⽅法:
1、在Controller中使⽤ThreadLocal变量
2、在spring配置⽂件Controller中声明 scope="prototype",每次都创建新的controller
所在在使⽤spring开发web 时要注意,默认Controller、Dao、Service都是单例的。
线程阻塞,单例模式的理解
以前⾃⼰理解不到位,单例模式下,只存在⼀个实例对象,那么在⾼并发的情况下,超级多的请求同时来访问这个类的同⼀个⽅法,那这个类忙的过来吗,还有这么多请求同时访问⼀个对象的⽅法,是不是要产⽣阻塞呢?
细细区分,其实是两个问题?
什么情况下产⽣线程阻塞 ?
线程阻塞发⽣在多个线程访问需要等待的资源的情形下,阻塞和是否是单例、多例是没有关系的。
单例多线程
现在对这个问题有了新的认识,其实在真正的应⽤场景中,⽐如springMVC中的controller,默认是单例的,但是web容器可是多线程的,这样,其实⼀个单例多线程对象在处理⾼并发的时候还是很容易的。举个不太恰当的例⼦,单例对象中的⽅法,就好⽐是菜谱,运⾏具体⽅法的线程,就好像是厨师对着同⼀个菜谱做菜,当然这个过程是并发完成的。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论