概念:单例对象的类只能允许一个实例存在。
实现步骤:
1.构造方法私有,防止外部代码块创建实例;
2.通过静态方法(getInstance)获取实例。在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
注意问题:
1.线程安全问题,避免创建多个实例;
2.效率问题;
3.内存资源浪费问题;
代码:
//饿汉模式
public class Sington {
private final static Sington sington = new Sington();
private Sington(){}
public static Sington getInstance(){
return sington;
}
//优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
//缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
}
public class Singleton {
private static Singleton instance ;
private Singleton(){}
//懒汉式
//线程不安全,可能会创建多个实例 不可用
public static Singleton getIntance(){
if(null == instance) {
instance = new Singleton();
}
return instance;
}
//解决了线程安全的问题 效率太低 每个线程想到获得实例 都需要 线程同步方法
public static synchronized Singleton getInstance2(){
if(null == instance) {
instance = new Singleton();
}
return instance;
}
//同步代码块 解决了效率问题 但是不能解决同步问题 可能会创建多个实例
public static Singleton getInstance3(){
if(null == instance) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
推荐使用:
//单例模式-双重保险 volatile 关键字
public class Single {
private static volatile Single single;
private Single (){
}
public Single getInstance(){
if (null == single){
synchronized (Single.class){
if (null == single){
single = new Single();
}
}
}
return single;
}
//内部静态类的形式 jvm 管理线程安全问题 推荐使用
/**
* 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
* 不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,
* 而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,
* 从而完成Singleton的实例化。
* 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
*
* 优点:避免了线程不安全,延迟加载,效率高
*/
private static class SingletonInstance {
private static final Single INSTANCE = new Single();
}
public static Single getInstance5(){
return SingletonInstance.INSTANCE;
}
}
volatile 关键字作用:解决内存指令重排序造成的问题。
1.看class对象是否加载,如果没有就先加载class对象,2.分配内存空间,初始化实例,3.调用构造函数,4.返回地址给引用。而cpu为了优化程序,可能会进行指令重排序,打乱这3,4这几个步骤,导致实例内存还没分配,就被使用了。
线程A执行到new Single()
,开始初始化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数。这时时间片结束了,切换到线程B执行,线程B调用new Single()
方法,发现引用不等于null
,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量。
加了volatile之后,就保证new
不会被指令重排序。