一.使用单例模式的场景:有些时候某个类的实例只需要一个而不需要多个的时候,比如一些Factory类。
二。常用的几种单例构造方法:
1.饿汉模式:
示例代码如下:
package cn.edu.tju.app;
import java.util.Date;
public class SimpleSingleton {
private static SimpleSingleton instance=new SimpleSingleton();
//构造方法,private,防止通过new来创建对象
private SimpleSingleton(){
}
//静态方法,用来获取单例类的实例
public static SimpleSingleton getInstance(){
return instance;
}
//业务方法
public String getCurrentTime(){
return new Date().toLocaleString();
}
}
在这种模式下,构造方法为private类型的,所以无法通过new来创建对象;而在类加载的时候就通过
private static SimpleSingleton instance=new SimpleSingleton();
创建了类的实例,之后可以通过调用getInstance()方法来获取这个实例,每次调用getInstance()得到的都是类加载时创建的这个实例。
测试代码如下:
package cn.edu.tju.app;
public class SimpleSingletonTest {
public static void main(String[] args) {
SimpleSingleton mySingleton01=SimpleSingleton.getInstance();
SimpleSingleton mySingleton02=SimpleSingleton.getInstance();
System.out.println(mySingleton01==mySingleton02);
String currentTime=mySingleton01.getCurrentTime();
System.out.println(currentTime);
}
}
执行后程序输出如下,
2.懒汉模式:(DCL单例)
实例代码如下:
package cn.edu.tju.app;
import java.util.Date;
public class ComplexSingleton {
private static volatile ComplexSingleton instance;
//构造方法,private,防止通过new来创建对象
private ComplexSingleton(){
}
//静态方法,用来获取单例类的实例
public static ComplexSingleton getInstance(){
if(instance==null){
synchronized (ComplexSingleton.class){
if(instance==null){
instance=new ComplexSingleton();
return instance;
}
}
}
return instance;
}
//业务方法
public String getCurrentTime(){
return new Date().toLocaleString();
}
}
在这种模式下,构造方法为private类型的,所以无法通过new来创建对象;而在类加载的时候,并未对instance实例进行初始化,
private static volatile ComplexSingleton instance;
当调用getInstance()方法时,才去创建对象并赋值给instance,然后返回instance.其中getInstance()方法中的synchronized关键字是防止在多线程访问条件下创建出多个实例,而volatile关键字是防止在多线程访问的条件下一个线程访问到另一个线程初始化了一半的对象(具体就是使用volatile防止指令重排序)。
测试代码如下:
package cn.edu.tju.app;
public class ComplexSingletonTest {
public static void main(String[] args) {
ComplexSingleton mySingleton01=ComplexSingleton.getInstance();
ComplexSingleton mySingleton02=ComplexSingleton.getInstance();
System.out.println(mySingleton01==mySingleton02);
String currentTime=mySingleton01.getCurrentTime();
System.out.println(currentTime);
}
}
运行结果如下
3.枚举模式
把单例类定义为枚举,而该枚举只有一个值。
实例代码如下:
package cn.edu.tju.app;
import java.util.Date;
public enum EnumSingleton {
INSTANCE;
public String getCurrentTime(){
return new Date().toLocaleString();
}
}
测试代码如下:
package cn.edu.tju.app;
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton mySingleton01=EnumSingleton.INSTANCE;
EnumSingleton mySingleton02=EnumSingleton.INSTANCE;
System.out.println(mySingleton01==mySingleton02);
String currentTime=mySingleton01.getCurrentTime();
System.out.println(currentTime);
}
}
运行结果如下:
三。这几种常用的单例构建模式真的能够保证单例吗?答案是只有枚举模式的单例能绝对保证单例,饿汉模式和懒汉模式的单例都可以通过反射来创建多个实例,而枚举单例无法创建多个实例。测试代码如下:
package cn.edu.tju.app;
import org.apache.tomcat.util.bcel.Const;
import java.lang.reflect.Constructor;
public class SingletonReflectionTest {
public static void main(String[] args) throws Exception {
//尝试用反射来测试DCL单例是否能真的只创建一个实例(懒汉模式)
Class clazz=ComplexSingleton.class;
Constructor constructor=clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
ComplexSingleton complexSingleton1=(ComplexSingleton)constructor.newInstance();
ComplexSingleton complexSingleton2=(ComplexSingleton)constructor.newInstance();
System.out.println(complexSingleton1==complexSingleton2);
//尝试用反射来测试枚举单例是否能真的只创建一个实例
Class myEnumClass=EnumSingleton.class;
Constructor[] constructors=myEnumClass.getDeclaredConstructors();
Constructor enumConstructor=constructors[0];
enumConstructor.setAccessible(true);
EnumSingleton enumSingleton1=(EnumSingleton)enumConstructor.newInstance();
EnumSingleton enumSingleton2=(EnumSingleton)enumConstructor.newInstance();
System.out.println(enumSingleton1==enumSingleton2);
}
}
运行程序输出如下:
通过程序输出的false,可以看出,懒汉模式的单例并不能保证真正的单例。通过程序输出的"Cannot reflectively create enum objects"可以看出并不能通过反射的方法来创建出单例枚举类的多个实例