后续我会陆陆续续更新设计模式的一些介绍、应用、以及原理的文档。大家如果觉得对自己有用就点个关注吧。
引言
本文中的例子我尽量写的简单,避免一些我平时查资料时一些例子中出现的大量无用代码,产生让人阅读不下去的感觉
单例模式
来!我们先讨论个问题,我先不和你们扯什么在Spring当中是否使用了单例模式,我就先问一下你们能理解"单" “例” “模” “式"这4个字吗?
其实不难理解,我看网上有人说"在容器当中一个对象只有一个实例,这种设计思想叫单例模式”,这么说对,但是不准确,这么说不就又把Spring的IoC容器整进去了吗,其实应该是"在整个系统当中一个对象只存在一个对象实例,这种设计模式叫单例模式".
优缺点
优点:
- 就一个对象实例,节省系统资源,减轻gc(垃圾回收器)压力
- 可以实现一些资源共享
代码实现思路
想实现一个单例模式,首先最大问题就是怎么才能让这个对象只被New一次,这个问题我们可以通过限制构造函数来达目的,将需要单例的对象,构造函数设置为私有的.这样以来只有我自己才能创建我自己.
public class Singleton {
private static Singleton singleton;
private Singleton(){
System.out.println("我是一个单利对象");
}
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
return singleton;
}
return singleton;
}
}
看上面的代码,由于构造器是私有的,所以任何一个对象都不能直接
new Singleton()
来创建对象,想获取Singleton
对象只有一种办法调用静态方法getSingleton()
而这个方法的功能就是只在第一次被调用时new一个Singleton
对象,之后无论谁再调用这个方法来获取Singleton
对象,都只会把之前的new号的对象返回.
知识点(创建类对象的加载顺序)
- 父类的静态代码块,静态变量的初始化
- 执行子类的静态代码块,静态变量的初始化
- 初始化父类实例变量
- 执行父类构造函数
- 初始化子类实例变量
- 执行子类构造函数
关于懒汉模式和饿汉模式
饿汉模式
顾名思义:就是在程序加载的时候就创建了这个对象,无论这个对象以后会不会被用到,它都会被加载.当然这样做有好处也有坏处.
好处:不用考虑线程问题.因为他是线程安全的.
坏处:如果数量太多会拖慢程序启动速度,如果某些对象到程序结束都没有用到.内存就会被浪费
代码示例
public class Singleton {
private final static Singleton singleton= new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return singleton;
}
}
懒汉模式
再次顾名思义:就是这个对象什么时候第一次用到它我才创建这个对象,否则一直不创建对象.
好处:会减少程序启动的负担,并且也会节省内容
坏处:这个写法相对比较费劲,因为你要确保他线程是安全的,不然就会出现两个请求同时访问这个方法,导致这个方法创建了2个以上的对象出来.(上面的那个例子就是懒汉模式,但是它线程不是安全的)
代码示例(不可以这么写)
先来一个线程不安全的示例
public class Singleton {
private static Singleton singleton;
private Singleton(){
System.out.println("我是一个单利对象");
}
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
return singleton;
}
return singleton;
}
}
这样写当同时两个请求调用
getSingleton()
程序就会创建2个Singleton对象
再来一个线程安全的写法
大体思路就是用锁(synchronized),当一个线程调用这个方法的时候,如果其他线程也想调用这个方法,那么他就必须等当前线程调用完成,才可以在去调用方法
public class Singleton {
private static Singleton singleton;
private Singleton(){
System.out.println("我是一个单利对象");
}
public static synchronized Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
return singleton;
}
return singleton;
}
}
这样写当然可以解决线程安全问题,但是这样就会造成每次调用这个方法时,这个方法都会被锁住,但其实我们只需要在方法第一次被调用,创建对象的时候锁住就行了.一旦单例对象被创建出来,这个方法就不需要在锁住了.所以我们优化一下
上个版本优化版
public class Singleton {
private static Singleton singleton;
private Singleton(){
System.out.println("我是一个单利对象");
}
public static Singleton getSingleton(){
if (singleton == null) {
synchronized(Singleton.class) {
//这里面必须在校验一遍,因为当有两个线程同时访问方法,
//第一个线程先进去创建对象,如果不加这层校验,
//第二个线程进去还是会创建一个对象
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
}
}
return singleton;
}
}
这里面为什么要多加一层校验?,因为当有两个线程同时访问方法,第一个线程先进去创建对象,如果不加这层校验,第二个线程进去还是会创建一个对象
再来一个高级版本
public class Singleton {
private Singleton(){
System.out.println("我是一个单利对象");
}
private static class SingletInside{
private static Singleton singleton = new Singleton();
}
private static Singleton getSingleton(){
return SingletInside.singleton;
}
}
这个方法说道就多了,看没看到类里面有一个静态内部类,之所以能这写是因为,在
Singleton
类被new出来以后,如果没有调用getSingleton()
方法那么这个静态内部类就不会被初始化,当第一次调用getSingleton()
方法时Singleton
单例对象也随着SingletInside
内部类的初始化而被创建出来.而之后再调用SingletInside
这个静态内部类就不会在初始化了(类在程序中只会加载一次)