所谓单例就是在系统中只有一个该类的实例。
单例模式的核心分以下三个步骤:
 * 构造方法私有化。即不能在类外实例化,只能在类内实例化。 
 * 在本类中创建本类的实例。 
 * 在本类中提供给外部获取实例的方式。 
单例模式的实现方式有两种:饿汉模式和懒汉模式。
饿汉模式
不管现在需不需要,先创建实例。关键在于“饿”,饿了就要立即吃。
静态常量
这里将类的构造器私有化,就不能在外部通过new关键字创建该类的实例,然后定义了一个该类的常量,用static修饰,以便外部能够获得该类实例(通过HungryStaticConstantSingleton.
INSTANCE获得)。也可以不加final关键字,具体看自己的需求。
 1 /**  2  * 恶汉模式-静态常量,简洁直观  3 */  4 public class 
HungryStaticConstantSingleton{ 5 //构造器私有化  6 private 
HungryStaticConstantSingleton() { 7  }  8 //静态变量保存实例变量 并提供给外部实例  9 public final 
static HungryStaticConstantSingleton INSTANCE = new 
HungryStaticConstantSingleton();10 } 
 
枚举
这种方式是最简洁的,不需要考虑构造方法私有化。值得注意的是枚举类不允许被继承,因为枚举类编译后默认为final 
class,可防止被子类修改。常量类可被继承修改、增加字段等,容易导致父类的不兼容。
/** * 恶汉-枚举形式,最简洁 */ public enum HungryEnumSingleton{ INSTANCE; public void 
print(){ System.out.println("这是通过枚举获得的实例"); System.out.println(
"HungryEnumSingleton.pring()"); } } 
 
Test,打印实例直接输出了【INSTANCE】,是因为枚举帮我们实现了toString,默认打印名称。
public class EnumSingleton2Test{ public static void main(String[] args) { 
HungryEnumSingleton singleton2= HungryEnumSingleton.INSTANCE; 
System.out.println(singleton2); singleton2.print(); } } 
 输出结果
 
静态代码块
这种方式和上面的静态常量/变量类似,只不过把new放到了静态代码块里,从简洁程度上比不过第一种。但是把new放在static代码块有别的好处,那就是可以做一些别的操作,如初始化一些变量,从配置文件读一些数据等。
/** * 恶汉模式-静态代码块 */ public class HungryStaticBlockSingleton{ //构造器私有化 private 
HungryStaticBlockSingleton() { }//静态变量保存实例变量 public static final 
HungryStaticBlockSingleton INSTANCE;static { INSTANCE = new 
HungryStaticBlockSingleton(); } } 
 
如下,在static代码块里读取 info.properties 配置文件动态配置的属性,赋值给 info 字段。
/** * 恶汉模式-静态代码块 * 这种用于可以在静态代码块进行一些初始化 */ public class 
HungryStaticBlockSingleton{private String info; private 
HungryStaticBlockSingleton(String info) {this.info = info; } //构造器私有化 private 
HungryStaticBlockSingleton() { }//静态变量保存实例变量 public static final 
HungryStaticBlockSingleton INSTANCE;static { Properties properties = new 
Properties();try { properties.load(HungryStaticBlockSingleton.class
.getClassLoader().getResourceAsStream("info.properties")); } catch (IOException 
e) { e.printStackTrace(); } INSTANCE= new 
HungryStaticBlockSingleton(properties.getProperty("info")); } public String 
getInfo() {return info; } public void setInfo(String info) { this.info = info; 
} } 
 
Test,
public class HungrySingletonTest{ public static void main(String[] args) { 
HungryStaticBlockSingleton hun= HungryStaticBlockSingleton.INSTANCE; 
System.out.println(hun.getInfo()); } } 
 
输出
 
懒汉模式
需要时再创建,关键在于“懒”,类似懒加载。
非线程安全
同样是构造方法私有化,提供给外部获得实例的方法,getInstance()方法被调用时创建实例。该方式适用于单线程,因为在多线程的情况下可能会发生线程安全问题,导致创建不同实例的情况发生。可以看下面的演示。
 1 /**  2  * 懒汉模式-线程不安全的,适用于单线程  3 */  4 public class LazyUnsafeSingleton{  5 
private LazyUnsafeSingleton(){  6  }  7 private static LazyUnsafeSingleton 
instance; 8 public static LazyUnsafeSingleton getInstance(){  9 if(instance==
null){ 10 instance = new LazyUnsafeSingleton(); 11  } 12 return instance; 13  } 
14 } 
非线程安全演示
 1 public class LazyUnsafeSingletionTest{  2 public static void main(String[] 
args)throws ExecutionException, InterruptedException {  3 ExecutorService es = 
Executors.newFixedThreadPool(2);  4 Callable<LazyUnsafeSingleton> c1 = new 
Callable<LazyUnsafeSingleton>(){  5  @Override  6 public LazyUnsafeSingleton 
call()throws Exception {  7 return LazyUnsafeSingleton.getInstance();  8  }  9  
};10 Callable<LazyUnsafeSingleton> c2 = new Callable<LazyUnsafeSingleton>(){ 11 
 @Override12 public LazyUnsafeSingleton call() throws Exception { 13 return 
LazyUnsafeSingleton.getInstance();14  } 15  }; 16 Future<LazyUnsafeSingleton> 
submit = es.submit(c1); 17 Future<LazyUnsafeSingleton> submit1 = es.submit(c2); 
18 LazyUnsafeSingleton lazyUnsafeSingleton = submit.get(); 19 
LazyUnsafeSingleton lazyUnsafeSingleton1 = submit1.get(); 20  es.shutdown(); 21 
22  System.out.println(lazyUnsafeSingleton); 23  
System.out.println(lazyUnsafeSingleton);24 
System.out.println(lazyUnsafeSingleton1==lazyUnsafeSingleton); 25  } 26 } 
 
输出 大概运行三次就会出现一次,我们可以在 LazyUnsafeSingleton 中判断 if(instance==null) 
之后增加线程休眠以获得更好的效果。
线程安全的
该方式是懒汉模式中线程安全的创建方式。通过同步代码块控制并发创建实例。并且采用双重检验,当两个线程同时执行第一个判空时,都满足的情况下,都会进来,然后去争锁,假设线程1拿到了锁,执行同步代码块的内容,创建了实例并返回,此时线程2又获得锁,执行同步代码块内的代码,因为此时线程1已经创建了,所以线程2虽然拿到锁了,如果内部不加判空的话,线程2会再new一次,导致两个线程获得的不是同一个实例。线程安全的控制其实是内部判空在起作用,至于为什么要加外面的判空下面会说。
/** * 懒汉模式-线程安全,适用于多线程 */ public class LazySafeSingleton{ private static 
volatile LazySafeSingleton safeSingleton;//防止指令重排 private LazySafeSingleton() { 
}public static LazySafeSingleton getInstance(){ if(safeSingleton==null){ 
synchronized (LazySafeSingleton.class){ if(safeSingleton==null){//双重检测 
safeSingleton =new LazySafeSingleton(); } } } return safeSingleton; } } 
 当不加内层判空时,会出现不是单例的情况,只不过出现的概率更低了点。
可不可以只加内层判空呢?答案是可以。
那为什么还要加外层判空的呢?
内层判空已经可以满足线程安全了,加外层判空的目的是为了提高效率。因为可能存在这样的情况:线程1拿到锁后执行同步代码块,在new之后,还没有释放锁的时候,线程2过来了,它在等待锁(此时线程1已经创建了实例,只不过还没释放锁,线程2就来了),然后线程1释放锁后,线程2拿到锁,进入同步代码块汇总,判空,返回。这种情况线程2是不是不用去等待锁了?所以在外层又加了一个判空就是为了防止这种情况,线程2过来后先判空,不为空就不用去等待锁了,这样提高了效率。
内部类创建外部类实例
该方式天然线程安全,是否final根据自己需要。
 1 /**  2  * 懒汉模式-线程安全,适用于多线程  3  * 在内部类被加载和初始化时 才创建实例  4  * 
静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独加载和初始化的。 5  * 因为是在内部类加载和初始化时创建的 因此它是线程安全的  6 */  
7 public class LazyInnerSingleton{  8 private LazyInnerSingleton() {  9  } 10 
private static class Inner{ 11 private static final LazyInnerSingleton INSTANCE 
=new LazyInnerSingleton(); 12  } 13 public static LazyInnerSingleton 
getInstance(){14 return Inner.INSTANCE; 15  } 16 } 
 
总结
饿汉模式
 * 静态常量 简洁直观容易理解 
 * 枚举 最简洁 
 * 静态代码块 可以在静态块里做一些初始化的工作 
懒汉模式
 * 单线程形式 该形式下不适用多线程,存在线程安全问题 
 * 多线程形式 适用于多线程 
 * 内部类形式 最简洁 
 
热门工具 换一换