单例(Singleton)模式是一种广泛使用的设计模式,单例模式的主要作用是保证在Java程序中,单例类只有一个实例存在(如:管理器和控制器)。单例模式能够避免实例被重复创建,能够避免由于操作多个实例导致的逻辑错误。如果一个对象可能贯穿整哦应用程序,并且起到全局统一管理控制的作用,那么单例模式是一个值得考虑的设计模式。
PS:为什么定义静态实例,在类加载的时候儿进行实例化,能避免线程同步问题?
因为在类加载过程中,一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕,退出<clinit>()后,其他阻塞的线程被唤醒后也不会再次进入<clinit>()方法,同一个类加载器下,一个类型只会被初始化一次。其中<clinit>()方法是由编译器自动收集类中的所有类变量赋值动作和静态语句块中的语句合并产生的(顺序由源文件中出现的顺序所决定)。
一、饿汉模式(多线程安全):
public class Singleton {
private static Singleton instance = new Singletin();//定义静态实例,在类加载的时候就进行实例化,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。
private Singleton() {//构造类定义为private,保证其他类不能实例化此类,只能在Singleton类的内部调用Singleton的构造方法构造Singleton实例;
public static Singleton getInstance() {//把静态实例返回给调用者;
return instance;
}
}
//饿汉式的缺点:即使单例没有被用到也会被创建,因为在类加载之后就创建了,内存就别浪费了。这种实现方式适合单例占内存较小,并且在初始化的时候儿就被用到的情况。但是如果单例占用的内存较大,或者只是在某个特定场景下才会用到,那么饿汉式就不合适了,需要用懒汉模式进行延迟加载。
二、懒汉式
2.1、线程不安全懒汉式:
public class Singleton{
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstace() {
if(null = instance) {
instance = new Singleton();
}
return instance;
}
}
//该版本的懒汉模式缺点:并没有考虑线程安全问题,在并发的情况下,在多线程环境下可能会并发调用它的getInstance方法,导致创建多个实例。
2.2、synchronized同步方法 线程安全懒汉模式
//加锁解决线程同步问题;(改进懒汉式)
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(Singleton == null) {
instance = new Singleton();
}
return instance;
}
}
//缺陷:看起来解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,synchronized修饰的同步方法要比一般方法慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了。
2.3、 双重校验锁 线程安全懒汉模式(Synchronize修饰的方法和代码块的区别,如2.4所示???????)
优点:实现了延迟加载,又解决了线程并发问题,同时也解决了效率问题。
缺点:由于指令重排优化,可能会导致程序运行过程中报错(详细解释见2.4)。
//双重校验锁,解决线程安全懒汉模式1中的,synchronized修饰的同步方法慢的缺点。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
2.4、valatile禁止指令重排优化的 线程安全懒汉式
由于Java中的指令重排优化,JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序优化。
在JVM中,instance = new Singleton();不具有原子性,可以分为3个原子操作:a、给instance分配内存(并且instance为空);b、调用Singleton构造函数,生成对象来初始化instance;c、将instance对象指向分配的内存。 (此处3个原子操作描述不清楚???????)
如果线程1的执行顺序时a->c->b,当线程1执行到c未完成b时『instance已经不为null但是仍没有完成初始化的中间过程』,如果被线程2强占了,那么线程2在if(instance == null)这儿判为真,就会直接返回instance。线程2直接使用instance,当然就会报错。
解决方法:
这种情况就会使得2.3中的双重校验实效,JDK1.5之后增加了volatile关键字。volatile禁止指令重排序优化,这就保证了instance变量instance变量被赋值(即让instance对象指向分配的内存地址时)的时候对象已经是初始化过的,从而避免了出现报错的问题。
public Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
3、静态内部内(Effective Java中提出的方法)
和同样利用类加载机制来保证只创建一个instance实例,不存在多线程不安全问题。不一样的是,它是在内部类里面去创建对象实例的,只要应用中不使用内部类(即:调用getInstance方法),也就不会创建单例对象,从而实现延迟加载(比如,应用在使用Singleton.f()时,并不会去创建Singleton实例-instance)。也就是说这种方式可以同时保证延迟加载和线程安全。
参考:https://www.jianshu.com/p/d020cad14722
public class Singleton {
private static class SingletonHolder {
public static Singleton instance = new Singleton();
}
private Singleton{}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
public static f() {}
}
参考链接:
https://www.cnblogs.com/dongyu666/p/6971783.html
http://blog.csdn.net/goodlixueyong/article/details/51935526