核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
常见场景:
优点:
常见的五种单例模式实现方式:
饿汉式代码:
package pers.mobian.singleton;
//饿汉式单例
public class SingletonTest01 {
//1.私有化构造器
private SingletonTest01() {
}
//2.类初始化的时候,立即加载该类的对象
private static SingletonTest01 instance = new SingletonTest01();
//3.提供获取该对象的方法,此处没有synchronized,效率高
public static SingletonTest01 getInstance() {
return instance;
}
}
class Test {
public static void main(String[] args) {
SingletonTest01 instance1 = SingletonTest01.getInstance();
SingletonTest01 instance2 = SingletonTest01.getInstance();
System.out.println(instance1 == instance2);
}
}
测试结果:
true
总结:
此种单例模式十分简单,但是如果在SingletonTest01类中,存在初始化的数据,每次仅仅初始化类,并未真真调用该类时,就会出现占用空间的现象。代码如下:
public class SingletonTest01 {
byte[] a1 = new byte[1024];
byte[] a2 = new byte[1024];
byte[] a3 = new byte[1024];
private SingletonTest01() {
}
private static SingletonTest01 instance = new SingletonTest01();
public static SingletonTest01 getInstance() {
return instance;
}
}
于是出现了懒汉式单例模式。
懒汉式代码:
package pers.mobian.singleton;
public class SingletonTest02 {
byte[] a1 = new byte[1024];
byte[] a2 = new byte[1024];
byte[] a3 = new byte[1024];
//1.私有化构造器
private SingletonTest02() {
}
//2.类初始化的时候,立即加载该类的对象
private static SingletonTest02 instance;
//3.提供获取该对象的方法,此处有synchronized,效率较低
public static synchronized SingletonTest02 getInstance() {
if (instance == null) {
instance = new SingletonTest02();
}
return instance;
}
}
class Test1 {
public static void main(String[] args) {
SingletonTest02 instance1 = SingletonTest02.getInstance();
SingletonTest02 instance2 = SingletonTest02.getInstance();
System.out.println(instance1 == instance2);
}
}
测试结果:
true
总结:
此方法可以延时加载,仅仅调用该方法后,才会初始方法区中的数据,继而避免出现饿汉式单例模式的问题。但是使用了synchronized关键字,在达到了线程安全的同时会出现效率低下的问题,继而引入了DCL(双重检测锁)懒汉式。
DCL懒汉式代码:
package pers.mobian.singleton;
public class SingletonTest03 {
//1.私有化构造器
private SingletonTest03() {
}
//2.类初始化的时候,立即加载该类的对象
//此处还需要加上volatile关键字:此关键字直接指向内存,防止在运行第三步的时候,出现多个线程运行到synchronized代码块后,if判断语句之前
private volatile static SingletonTest03 instance;
//因为此操作不是原子性操作,所以在内存中会出现这样三步的操作
/*
* 1.分配内存
* 2.执行构造方法
* 3.指向地址
* */
//这也就是为什么要使用volatile关键字
//3.提供获取该对象的方法,此处没有synchronized,效率较低
public static SingletonTest03 getInstance() {
//此处为第一重锁,判断对象是否被创建
if (instance == null) {
synchronized (SingletonTest03.class) {
//此为第二重锁,判断自己是否是第一个拿到锁的,继而创建对象
if (instance == null) {
instance = new SingletonTest03();
}
}
}
return instance;
}
}
class Test3 {
public static void main(String[] args) {
SingletonTest03 instance1 = SingletonTest03.getInstance();
SingletonTest03 instance2 = SingletonTest03.getInstance();
System.out.println(instance1 == instance2);
}
}
测试结果:
true
总结:
使用此单例模式,可以避免出现懒汉式单例synchronized关键字将整个方法锁住,继而影响效率。DCL单例中的synchronized关键字只锁了if判断成功以后的代码块,如果instance不是null,则会跳过直接return,不会降低效率。
饿汉式改进、静态内部类式代码:
package pers.mobian.singleton;
//静态内部类实现
public class SingletonTest04 {
private SingletonTest04(){}
private static class InnerClass{
private static final SingletonTest04 instance = new SingletonTest04();
}
public static SingletonTest04 getInstance(){
return InnerClass.instance;
}
}
class Test4{
public static void main(String[] args) {
SingletonTest04 instance1 = SingletonTest04.getInstance();
SingletonTest04 instance2 = SingletonTest04.getInstance();
System.out.println(instance1 == instance2);
}
}
测试结果:
true
总结:
采用此方法,可以延时加载,并且线程安全。但是java中存在反射机制,能够改变内部的private关键字,于是引入了枚举单例模式
反射破坏单例模式测试代码:
package pers.mobian.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//静态内部类实现
public class SingletonTest04 {
private SingletonTest04(){}
private static class InnerClass{
private static final SingletonTest04 instance = new SingletonTest04();
}
public static SingletonTest04 getInstance(){
return InnerClass.instance;
}
}
class Test4{
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
//利用反射获取其对应的构造器方法
Constructor<SingletonTest04> declaredConstructor = SingletonTest04.class.getDeclaredConstructor(null);
//开启访问权限
declaredConstructor.setAccessible(true);
//直接调用方法,进行实例化输出
SingletonTest04 instance1 = SingletonTest04.getInstance();
SingletonTest04 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
执行结果:
false
2137211482
920011586
newInstance类源码:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, clazz, modifiers);
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
//此行代码表示,不能反射枚举类型的对象
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
枚举单例测试代码:
package pers.mobian.singleton;
public enum SingletonTest05 {
INSTANCE;
public SingletonTest05 getInstance(){
return INSTANCE;
}
}
class test05{
public static void main(String[] args) {
SingletonTest05 instance1 = SingletonTest05.INSTANCE;
SingletonTest05 instance2 = SingletonTest05.INSTANCE;
System.out.println(instance1==instance2);
}
}
测试结果:
true
总结:
利用枚举的方式创建单例模式,可以避免反射对其内部进行关键字的破坏,继而导致单例模式失效。它的缺点也是不能延时加载
不同的单例模式拥有不同的运用场景,灵活运用。将单例模式的思想带入到我们的编码中,才是学习设计模式的关键。