由于工作原因,导致我断了一阵子的设计模式学习,今天我们来继续设计模式的学习。今天我们要了解的是单例模式——单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
这种模式的类,类负责创建自己的对象,同时确保只有单个对象被创建。并且提供一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
entity
public class SingleObject {
//创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();
//让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}
//获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
demo
public class SingletonPatternDemo {
public static void main(String[] args) {
//不合法的构造函数
//编译时错误:构造函数 SingleObject() 是不可见的
//SingleObject object = new SingleObject();
//获取唯一可用的对象
SingleObject object1 = SingleObject.getInstance();
SingleObject object2 = SingleObject.getInstance();
System.out.println(object1);
System.out.println(object2);
}
}
结果
在知道了什么是单例模式后,我想小伙伴们一定会想到静态类,说:“既然只使用一个对象,为何不干脆使用静态类?”,这里我会将单例模式和静态类进行一个比较。
public class HungrySingle {
private final static HungrySingle instance = new HungrySingle();
private HungrySingle() {
System.out.println(Thread.currentThread().getName());
}
public static HungrySingle getInstance() {
return instance;
}
}
歩骤
优点
缺点
建议
public class UnsafeLazySingle {
private static UnsafeLazySingle instance;
private UnsafeLazySingle() {
System.out.println(Thread.currentThread().getName());
}
public static UnsafeLazySingle getInstance() {
if (null == instance) {
instance = new UnsafeLazySingle();
}
return instance;
}
}
优点
缺点
建议
class LazySingleF{
private static LazySingleF instance;
private LazySingleF(){
System.out.println(Thread.currentThread().getName());
}
public static synchronized LazySingleF getInstance() {
if (instance == null) {
instance = new LazySingleF();
}
return instance;
}
}
优点
缺点
建议
class LazySingleB{
private static LazySingleB instance;
private LazySingleB() {
System.out.println(Thread.currentThread().getName());
}
public static LazySingleB getInstance() {
if (instance == null) {
synchronized (LazySingleB.class) {
instance = new LazySingleB();
}
}
return instance;
}
}
缺点
建议
class LazySingleD{
private volatile static LazySingleD instance;
private LazySingleD() {
System.out.println(Thread.currentThread().getName());
}
public static LazySingleD getInstance() {
if (null == instance) {
synchronized (LazySingleD.class) {
if (instance ==null) {
instance = new LazySingleD();
}
}
}
return instance;
}
}
优点
缺点
建议
注意
由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况。具体分析如下:
INSTANCE = new SingleTon();
这个步骤,其实在jvm里面的执行分为三步:
由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为
private volatile static LazySingleD instance;
就可解决DCL失效问题。volatile的内存栅栏功能,告知编译器的在标记的变量前后不使用优化功能,禁止指令重排序。
class InnerClass {
private InnerClass() {
System.out.println(Thread.currentThread().getName());
}
private static class InnerClassHolder {
private static InnerClass instance = new InnerClass();
}
public static InnerClass getInstance() {
return InnerClassHolder.instance;
}
}
优点
缺点
建议
JAVA虚拟机在有且仅有的5种场景下会对类进行初始化:
遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:
new一个关键字或者一个实例化对象时
读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)
调用一个类的静态方法时。
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上是类的主动引用。除此之外,所有引用类都不会对类进行初始化,称为被动引用。
静态内部类就属于被动引用的行列,为什么?
我们再回头看下getInstance()方法,调用的是InnerClassHolder.instance,取的是InnerClassHolder里的instance对象,跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,故不管多少个线程去调用getInstance()方法,取的都是同一个instance对象,而不用去重新创建。
当getInstance()方法被调用时,InnerClassHolder才在InnerClass的运行时常量池里,把符号引用替换为直接引用,这时静态对象instance也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式。
那么instance在创建过程中又是如何保证线程安全的呢?
虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。
可以看出INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
enum Enums {
/**
* 实例对象
*/
INSTANCE;
/**
* 两数相加之和方法
* @param a
* @param b
* @return
*/
public int add(int a, int b)
{
return a + b;
}
}
优点
以上就是我个人关于 设计模式——单例模式 的一些笔记,如果有什么问题,可以将问题发我邮箱 luodiab@126.com ,欢迎各位的意见。