单例模式基本定义:程序运行时,在java虚拟机中只存在该类的一个实例对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
目录
饿汉式
懒汉式(线程不安全,多线程不推荐使用)
懒汉式(线程安全,同步方法,也不推荐用,效率底)
双重检测(推荐使用)
静态内部类方式(推荐使用)
枚举(推荐使用)
饿汉式
- 构造器私有化(不可被 new)
- 类的内部创建对象(类初始化时立刻实例化,可能会浪费内存)
- 对外部暴露一个静态的公共方法 getInstance
public class ClassA {
//1.私有化构造方法,外部不能new,限制产生多个对象
private ClassA(){ }
//2.在类的内部创建一个类的实例
//类初始化时,立即实例化这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static final ClassA instance = new ClassA();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
//方法没有同步,调用效率高!
public static ClassA getInstance(){
return instance;
}
//测试
public static void main(String[] args) {
ClassA a = ClassA.getInstance();
ClassA b = ClassA.getInstance();
System.out.println(a==b);
}
}
优点:
这种方法在类加载的时候就进行实例化,避免线程同步问题;
缺点:
在类加载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果,如果从开始都结束从未使用该实例,则会导致线程浪费
- 构造器私有化(不可被 new)
- 类的内部创建对象(类初始化时立刻实例化,可能会浪费内存)
- 对外部暴露一个静态的公共方法 getInstance
public class ClassA {
//1.私有化构造方法,外部不能new,限制产生多个对象
private ClassA(){ }
//2.在类的内部创建一个类的实例
//类初始化时,立即实例化这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static final ClassA instance = new ClassA();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
//方法没有同步,调用效率高!
public static ClassA getInstance(){
return instance;
}
//测试
public static void main(String[] args) {
ClassA a = ClassA.getInstance();
ClassA b = ClassA.getInstance();
System.out.println(a==b);
}
}
优点:
这种方法在类加载的时候就进行实例化,避免线程同步问题;
缺点:
在类加载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果,如果从开始都结束从未使用该实例,则会导致线程浪费
懒汉式(线程不安全,多线程不推荐使用)
用到的时候才实例化
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
特点:
起到Lazy Loading效果,但是只能在单线程下使用。
如果在多线程下,一个线程在执行 if(instance == null) 的时候还未来得及往下执行,另一个线程也通过这调判断语句,这样就会产生多个实例,所以在多线程下不可使用。
结论:开发中 不要 使用
用到的时候才实例化
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
特点:
起到Lazy Loading效果,但是只能在单线程下使用。
如果在多线程下,一个线程在执行 if(instance == null) 的时候还未来得及往下执行,另一个线程也通过这调判断语句,这样就会产生多个实例,所以在多线程下不可使用。
结论:开发中 不要 使用
懒汉式(线程安全,同步方法,也不推荐用,效率底)
添加 synchronized
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static synchronized ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
特点:
解决线程不安全问题
效率低,每个线程在获取类的实例时,执行getInstance()方法都要进行同步,而其实可以只执行一次实例化代码,后面需要获得该类实例直接return就行,所以该方法效率低
结论:
结论:开发中 不推荐 使用
添加 synchronized
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static synchronized ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
特点:
解决线程不安全问题
效率低,每个线程在获取类的实例时,执行getInstance()方法都要进行同步,而其实可以只执行一次实例化代码,后面需要获得该类实例直接return就行,所以该方法效率低
结论:
结论:开发中 不推荐 使用
双重检测(推荐使用)
public class ClassE {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassE(){ }
//2.在类的内部创建一个类的实例
//volatile作用:解决命令重排问题
private volatile static ClassE instance;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassE getInstance(){
if(instance == null){ //检查实例,如果为空,就进入同步代码块
synchronized (ClassE.class){
if(instance == null){ //再检查一次,仍未空才创建实例
instance = new ClassE();
}
}
}
return instance;
}
//测试
public static void main(String[] args) {
ClassE a = ClassE.getInstance();
ClassE b = ClassE.getInstance();
System.out.println(a==b);
}
}
线程安全,加volatile的作用是禁止指令重排。
public class ClassE {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassE(){ }
//2.在类的内部创建一个类的实例
//volatile作用:解决命令重排问题
private volatile static ClassE instance;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassE getInstance(){
if(instance == null){ //检查实例,如果为空,就进入同步代码块
synchronized (ClassE.class){
if(instance == null){ //再检查一次,仍未空才创建实例
instance = new ClassE();
}
}
}
return instance;
}
//测试
public static void main(String[] args) {
ClassE a = ClassE.getInstance();
ClassE b = ClassE.getInstance();
System.out.println(a==b);
}
}
线程安全,加volatile的作用是禁止指令重排。
静态内部类方式(推荐使用)
有点类似饿汉式,但又能做到了延迟加载
public class ClassC {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassC(){ }
//2.在类的内部创建一个类的实例
private static class Holder{
private static ClassC instance = new ClassC();
}
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassC getInstance(){
return Holder.instance;
}
//测试
public static void main(String[] args) {
ClassC a = ClassC.getInstance();
ClassC b = ClassC.getInstance();
System.out.println(a==b);
}
}
静态内部类(Holder)在外部类(ClassC)被装载的时候不会被实例化,当调用getInstance时,才会装载Holder,从而完成ClassC才会被实例化,且创建过程的线程安全性,由 JVM 来保证
线程安全,利用静态内部类实现延迟加载,效率高
有点类似饿汉式,但又能做到了延迟加载
public class ClassC {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassC(){ }
//2.在类的内部创建一个类的实例
private static class Holder{
private static ClassC instance = new ClassC();
}
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassC getInstance(){
return Holder.instance;
}
//测试
public static void main(String[] args) {
ClassC a = ClassC.getInstance();
ClassC b = ClassC.getInstance();
System.out.println(a==b);
}
}
静态内部类(Holder)在外部类(ClassC)被装载的时候不会被实例化,当调用getInstance时,才会装载Holder,从而完成ClassC才会被实例化,且创建过程的线程安全性,由 JVM 来保证
线程安全,利用静态内部类实现延迟加载,效率高
枚举(推荐使用)
1
2
public enum ClassD {
//定义一个枚举的元素,它就代表了Singleton的一个实例。
INSTANCE;
//对外部提供调用方法:将创建的对象返回,只能通过类来调用
public void otherMethod(){
//功能处理
}
}
//测试
public static void main(String[] args) {
ClassD a = ClassD.INSTANCE;
ClassD b = ClassD.INSTANCE;
System.out.println(a==b);
}
线程安全,实现简单,调用效率高,不能延时加载。
枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。
1
2
public enum ClassD {
//定义一个枚举的元素,它就代表了Singleton的一个实例。
INSTANCE;
//对外部提供调用方法:将创建的对象返回,只能通过类来调用
public void otherMethod(){
//功能处理
}
}
//测试
public static void main(String[] args) {
ClassD a = ClassD.INSTANCE;
ClassD b = ClassD.INSTANCE;
System.out.println(a==b);
}
线程安全,实现简单,调用效率高,不能延时加载。
枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。



