一、问题场景
针对唯一递增ID号码生成器,如果需要时就直接创建一个ID对象,当程序中有两个对象,那就会存在生成重复ID的情况,此外频繁创建ID对象,也会消耗内存空间。
二、问题分析
ID生成器类是全局使用的类,会被频繁创建和销毁,为了节省内存空间,就要控制创建对象的数量,保证一个类只有一个对象,并提供一个全局的访问点。这就是创建型设计模式中的单例模式。
三、单例定义
单例设计模式指一个类只允许创建一个对象,并提供一个全局访问该对象的静态方法。
四、单例实现方式
为了保证这个对象只有一个,不能被外部程序创建,这个对象的构造函数必须是私有的,只能当前类内部创建。
1、饿汉式
public class SingleTon {
/**
* 将构造器私有化,防止直接new
*/
private SingleTon(){
}
/**
* 方式一:静态常量方式
* SingleTon对象实例在定义静态常量时创建
* 在类装载时就完成实例化
*/
private static SingleTon instance = new SingleTon();
// /**
// * 方式二: 静态代码块方式
// * SingleTon对象实例的创建放在静态代码块中
// * 当类装载的时候,就执行静态代码块中的代码,初始化SingleTon对象
// */
// private static SingleTon instance;
// static {
// instance = new SingleTon();
// }
/**
* 提供一个public的静态方法,返回instance
* @return
*/
public static SingleTon getInstance(){
return instance;
}
}
在类加载时创建并初始化好对象。这种创建是线程安全的,不过不支持延迟加载。
结论:不推荐实际开发中使用
2、懒汉式
public class SingleTonLazyLoading {
/**
* 将构造器私有化,防止直接new
*/
private SingleTonLazyLoading(){
}
private static SingleTon instance;
/**
* 调用获取单例对象方法时,再创建对象,实现延迟加载
* 加锁和判空是为了线程安全,防止二次创建对象
* @return
*/
public static Synchronized SingleTonLazyLoading getInstance(){
if(instance == null){
instance = new SingleTon();
}
return instance;
}
}
为了支持懒加载,在获取单例对象方法时再创建对象;
为了支持线程安全,只创建一个对象,在获取单例对象方法上加锁synchronized,同时方法里判断对象是否存在,存在则直接返回。
懒汉式支持懒加载,但是每次调用该方法,需要频繁加锁,释放锁,有并发度低的问题。
结论:不推荐实际开发中使用
3、双重检测
public class SingleTonDoubleCheck {
/**
* 将构造器私有化,防止直接new
*/
private SingleTonDoubleCheck(){
}
private static SingleTon instance;
public static SingleTonDoubleCheck getInstance(){
if(instance == null){
synchronized (SingleTonDoubleCheck.class){
if(instance == null){
instance = new SingleTon();
}
}
}
return instance;
}
}
懒汉式并发度低,是因为频繁加锁和释放锁,可以将方法锁替换成同步代码块锁来缩小加锁的范围,从而降低加锁的频率。
第一次检测,是为了判断对象是否存在,存在则直接返回对象。
第二次检测,是为了防止二次创建对象。如果两个线程都通过了第一次检测,其中一个线程先获取锁,创建好对象之后释放锁。然后,第二个线程获取到锁,如果不加第二次检测,就会二次创建对象。
这种实现方式解决了并发度低的问题,既支持延迟加载,也支持高并发。
结论:推荐实际开发中使用
4、静态内部类
public class SingleTonStaticInnerClass {
/**
* 将构造器私有化,防止直接new
*/
private SingleTonStaticInnerClass(){
}
private static class SingleTonInstance {
private static final SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass();
}
public static SingleTonStaticInnerClass getInstance(){
return SingleTonInstance.instance;
}
}
饿汉式保证了线程安全,但是不支持延迟加载。
要想实现延迟加载,还可以利用静态内部类的加载特点:在外部类加载的时候,静态内部类
并不会被加载,只有在程序中调用静态内部类的时候才加载。
此外,类装载的机制又保证初始化实例时只有一个线程
静态内部类实现方式,既避免了线程不安全,又实现了延迟加载,效率高
结论:推荐实际开发中使用
5、枚举
public class Singleton {
private Singleton(){
}
public static enum SingletonEnum {
SINGLETON;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
}
因为Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
这种借助JDK1.5中添加的枚举来实现的单例模式,不仅能避免多线程同步问题,而且还能防
止反序列化重新创建新的对象。
结论:推荐实际开发中使用
五、单例的应用
在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
//....
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
//...
}
六、单例的使用场景
1、需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等
2、表示全局唯一的类。从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
比如,配置信息类。在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份。
七、问题实现
单例模式已经讲解清楚,那开篇提到的唯一递增ID对象的单例模式怎么设计实现呢?你自己可以先试试实现一下,下期文章揭晓答案。