面试必问之单例模式!
单例模式:
所谓单例,就是整个程序有且只有一个实例。
该类负责创建自己的对象,同时要确保只有一个对象被创建,好处就是一个类你只能创建一个实例对象会节约内存开销,一般常用在工具类的实现
。
特点
.类构造器私有
.持有自己类型的属性
.对外提供获取实例的静态方法
懒汉模式:
package day01;
/**
* 懒汉模式:延迟初始化,线程不安全,严格意义上说不是单例模式
* 单例模式获取实例的三个要素:
* 1.类构造器私有
* 2.持有自己类型的属性
* 3.对外提供获取实例的静态方法
* @author 直条纹先生
*
*/
public class Singleton {
//2.持有自己类型的属性
private static Singleton instance;
//1.类构造器私有
private Singleton(){}
//3.对外提供获取实例的静态方法
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
package day01;
public class Test {
public static void main(String[] args) {
Singleton single1 = Singleton.getInstance();
Singleton single2 = Singleton.getInstance();
//判断地址一致返回true,论证是同一个对象实例
System.out.println(single1==single2);
}
}
饿汉模式:
线程安全,比较常用,但容易产生垃圾,因为一开始加载类的时候就初始化了实例
package day02;
/**
* 饿汉模式:一开始就初始化
* 比较消耗对象资源
* @author 直条纹先生
*
*/
public class Singleton {
//持有自己类型的属性
//static 静态修饰的属性在类加载的时候初始化,并只会加载一次
private static Singleton instance = new Singleton();
//类构造器私有
private Singleton(){}
//对外提供获取实例的静态方法
public static Singleton getInstance(){
return instance;
}
}
package day02;
public class Test {
public static void main(String[] args) {
Singleton single1 = Singleton.getInstance();
Singleton single2 = Singleton.getInstance();
//判断地址一致返回true,论证是同一个对象实例
System.out.println(single1==single2);
}
}
双检测锁模式实现单例:
双检锁模式也叫双重校验锁,综合了懒汉模式和饿汉模式两者的优缺点整合而成。
public class Singleton{
//volatile
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
Synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
双重锁模式,进行两次判断,第一次判断是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。
由于Singleton instance()对象在创建的时候jvm中可能会重新排序,在多线程访问下存在风险。使用volite修饰
instance实例操作不会被jvm重排序。解决该问题
内部类模式实现单例:
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return Inner.instance;
}
//内部类
public static class Inner{
private static final Singleton instance = new Singleton();
}
}
只有第一次调用getInstance()方法时,虚拟机才加载Inner并初始化instance,
只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。【目前此方式是所有单例模式中最值得推荐的一种方式】
通过枚举实现单例:(基本不用)
package day05;
/**
*
* 默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
* 枚举类隐藏了私有的构造器。
* 枚举类的域是相应类型的一个实例对象
* 那么枚举类型日常用例是这样子的:
* @author 直条纹先生
*
*/
public enum Singleton {
Instance;
//doSomething 该实例支持的行为
//可以省略此方法,通过Singleton.INSTANCE进行操作
public static Singleton getInstance(){
return Singleton.Instance;
}
}