单例模式:
顾名思义就是只能有一个,不能在出现第二个。就如同地球上没有两片完全一模一样的树叶一样。
程序猿的角度理解:一个类有且只能有一个实例,不能出现第二个,并且整个项目系统中都能访问该实例。
面试官:为啥不能出现第二个?
程序猿:。。。(这个面试官是笨蛋,出现第二个那就不叫单例模式了,那至少得叫双例模式,是吧?)
面试官:为啥整个项目系统中都能访问该实例?
程序猿:不能访问该实例,那new出来还有什么意义。
面试官:那你给我写个单例模式吧
程序猿:写就写,这能难倒我,哼,先写个懒汉式单例(第一种)
package com.tang.study.singleton;
/**
* 单例模式
* 懒汉式单例
* Created by tgp on 2018/4/15.
*/
public class SingletonPatternOne {
//关键点0:构造函数是私有的
private SingletonPatternOne(){}
///关键点1:声明单例对象是静态的
private static SingletonPatternOne instance = null;
//通过静态方法来构造对象
public static SingletonPatternOne getInstance(){
if(instance == null){//关键点2:判断单例对象是否已经被构造
instance = new SingletonPatternOne();
}
return instance;
}
public void showMessage(){
System.out.println("懒汉式单例:啥时候用啥时候NEW。");
}
}
面试官:你这代码遇到多线程的并发条件下就不安全了吧。在if判断那里,如果两个线程同时访问,有可能给new出多个单例实例,都多个了,还是屁的“单例”啊。
程序猿:(额,好像也是,幸亏我还有点干货,再撸一段),那我给您再写一段(第二种)
/**
* 单例模式
* 饿汉式单例
* Created by tgp on 2018/4/15.
*/
public class SingletonPatternTwo {
//关键点0:构造函数是私有的
private SingletonPatternTwo(){
}
///关键点1:声明单例对象是静态的
private static SingletonPatternTwo instance = new SingletonPatternTwo();
//通过静态方法来构造对象
public static SingletonPatternTwo getInstance(){
return instance;
}
public void showMessage(){
System.out.println("饿汉式单例:管你用不用,我都先给你new出来,用不用是你的事,一劳永逸,省的你每次用时都来烦我。");
}
面试官:那你给我讲讲你写的这种单例模式的优缺点
程序猿:
优点:这种写法在类加载的时候就完成对象的实例化,避免了线程不安全的问题。
缺点:在类加载的时候就完成了实例化,如果这个类一直没用,就会造成内存的浪费。
面试官:这种方式是解决了线程不安全的问题,但内存浪费总是不太好吧,尤其是服务器的内存都是很贵的
程序猿:好吧,那我给您优化优化(第三种)
public class SingletonPatternThree {
//关键点0:构造函数是私有的
private SingletonPatternThree(){}
///关键点1:声明单例对象是静态的
private static SingletonPatternThree instance = null;
//通过静态方法来构造对象
public static synchronized SingletonPatternThree getInstance(){
if(instance == null){//关键点2:判断单例对象是否已经被构造
instance = new SingletonPatternThree();
}
return instance;
}
public void showMessage(){
System.out.println("懒汉式加锁:第一次调用才初始化,避免内存浪费,同时线程安全");
}
}
面试官:内存浪费和线程安全的问题都解决了,那方法加锁会影响效率,多线程的情况,第一个访问方法期间,其它用户只能等待,用户体验太差
程序猿:(有完没完了。。)逼我放大招(第四种:双重检查)
//通过静态方法来构造对象
public static SingletonPatternFour getInstance(){
if(instance == null){//关键点2:判断单例对象是否已经被构造
synchronized(SingletonPatternFour.class){//关键点3:加锁
if(instance == null){//关键点4:二次判断单例是否已经被构造
instance = new SingletonPatternFour();
}
}
}
return instance;
}
public void showMessage(){
System.out.println("进行了两次if (instance == null)检查,这样就可以保证线程安全了。" +
"这样,实例化代码只用执行一次,后面再次访问时,判断if (instance == null),直接return实例化对象。");
}
面试官:这种不错,那你还会其它的实现方法吗?
程序猿:(我会的可多了,都拿出来怕吓着你)我还会几种别的(第五种:静态内部类)
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
程序猿:类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。优点:避免了线程不安全,延迟加载,效率高。
(第六种:枚举)
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
经验之谈:
一般情况下,推荐双检锁方式和静态内部类。