单例模式:一个类只能有一个实例对象,对外提供一个访问实例的全局访问点
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
单例模式的应用场景:
1、Spring容器中创建bean实例对象就是一种单例模式
2、servlet中每个servlet的实例
3、spring mvc和struts1框架中,控制器对象是单例模式
4、应用程序的日志应用,一般都是用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
5、操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统
6、项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
7、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
单例模式之饿汉式
构成单例的条件:
1、保证不能让其他人使用构造器创建对象
2、提供一个静态方法,用于返回该对象
饿汉式单例模式:在类被加载的时候就创建好了实例
package com.zhouym.singletonpattern;
public class HungryMan {
//类一加载就创建实例对象
private static HungryMan instance = new HungryMan();
//创建私有构造器,防止外界实例化
private HungryMan(){}
//对外提供共有的方法,返回实例对象
public static HungryMan getInstance(){
return instance;
}
}
观察下上述代码的写法,会不会被什么破坏掉,仍然可以创建多个对象,答案是可以的,我们可以通过反射和序列化(后续整理)
通过反射获取对象
package com.zhouym.singletonpattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class singletonpatternTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<?> classType = HungryMan.class;
Constructor<?> c = classType.getDeclaredConstructor();
c.setAccessible(true);
HungryMan c1 = (HungryMan)c.newInstance();
HungryMan c2 = HungryMan.getInstance();
System.out.println(c1==c2);
}
}
输出结果为:
说明c1与c2是两个对象,那么单例模式的只能创建一个实例对象的说法就不存在了,那么如何改进饿汉式的写法呢
改进后的单例模式之懒汉模式
package com.zhouym.singletonpattern;
public class HungryMan {
//类一加载就创建实例对象
private static HungryMan instance = new HungryMan();
//创建私有构造器,防止外界实例化
private HungryMan(){
//增加一个如果这个实例对象存在了,就抛出一个异常,不能使其创建对象
if (instance != null) {
throw new RuntimeException();
}
}
//对外提供共有的方法,返回实例对象
public static HungryMan getInstance(){
return instance;
}
}
结果:
单例模式之懒汉式
懒汉式单例模式:在类的对象第一次使用时才创建该实例对象
package com.zhouym.singletonpattern;
public class Slacker {
private static Slacker instance = null;
private Slacker(){}
public static Slacker getInstance(){
if (instance == null) {
synchronized (Slacker.class) {
if(instance == null){
instance = new Slacker();
}
}
}
return instance;
}
}
单例模式之双重检测锁式
public class SingletonInstance {
// 声明此类型的变量,但没有实例化
private static SingletonInstance instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance(){}
// 对外提供一个获取实例的静态方法,
public static SingletonInstance getInstance(){
if(instance == null){
SingletonInstance s = null;
synchronized(SingletonInstance.class){
s = instance;
if(s == null){
synchronized(SingletonInstance.class){
if(s == null){
s = new SingletonInstance();
}
}
}
instance = s;
}
}
return instance;
}
}
这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
静态内部类式
package com.zhouym.staticclass;
public class SingletonInstance {
//构造方法私有
private SingletonInstance() {}
//静态内部类
private static class SingletonClassInstance{
//声明外部类型的静态常量
public static final SingletonInstance si = new SingletonInstance();
}
//对外提供唯一获取实例的方法
public static SingletonInstance getInstance() {
return SingletonClassInstance.si;
}
}
1、外部类没有static属性,则不会像饿汉式那样立即加载对象。
2、只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
3、兼备了并发高效调用和延迟加载的优势!– 兼备了并发高效调用和延迟加载的优势!