GOF23 设计模式之单例模式

什么是单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

使用场景

当您想控制实例数目,节省系统资源的时候(即内存中只有一份实例对象)。

实现方式

  1. 构造方法私有化(不允许其他程序new对象)

    我们知道创建对象的时候一般是通过new关键字,使用类的构造器生成实例对象,例:new Object(); public Object() { }; 我们之所以能够通过这种方式创建对象是因为构造器的修饰符是Public;所以第一步就要把构造器私有化,即private Object() { } 。

  2. 在类内部创建类的实例

    因为构造器被私有化了,除了在类的内部能使用,其他地方都使用不了,所以类内部要创建一份实例对象。

3.让外部能获取类中的实例对象。

实现代码

这里分为饥饿加载和懒加载,先看饥饿加载的方式
可用

public class Test{
	//内部提供实例
    private static Test test = new Test();
	//所有的构造方法私有化,
	private Test(){}
	//提供对外的静态方法
    public static Test getInstance(){
        return test;
    }
}

可用

public class Test{
	//上面的变换写法,这里是通过静态代码块做初始化
    private static Test test = null;

	private Test(){}
    
    static{
		test = new Test();
	}
	
    public static Test getInstance(){
        return test;
    }
}

提问1:外部类可以通过类名.静态属性,和类名.静态方法的方式进行访问,为什么不把实例对象test 定义为public,变成下面这种方式?(答案见最后)

public class Test{
	//将 private 变为 public
    public static Test test = new Test();

	private Test(){}
	
    //public static Test getInstance(){
    //   return test;
    //}
}

懒加载
不可用,线程不安全

public class Test{

    private static Test instance = null;

	private Test(){}
	
	//只有在第一次调用getInstance()方法时才会生成实例对象
    public static Test getInstance(){
    //多个线程访问的时候,比如:线程a进入if(instance == null)时instance为null,
	//进入if内部,但还没有执行instance = new Test();,
	//线程b这时进入if(instance == null)这时因为线程a还没有执行instance = new Test();
	//所以instance也是null,也进入了if内部,导致线程a,b各创建了实例一次,
	// a创建instance = new Test();b创建instance = new Test();明显b创建的对象会将
	//a创建的对象替换掉了
	//这时a创建的对象也在内存中,b创建的对象也在内存中,这就有多个对象了。
       if(instance == null){
       		instance = new Test();
		}
       return instance ;
    }
}

线程安全,效率低

public class Test{

    private static Test instance = null;

	private Test(){}
	
	//对getInstance()方法加锁,因为同时只能有一条线程执行这个方法,所以效率低
    public static synchronized Test getInstance(){

       if(instance == null){
       		instance = new Test();
		}
       return instance ;
    }
}

线程不安全,不可用

public class Test{

    private static Test instance = null;

	private Test(){}
	
	//对getInstance()方法加锁,因为同时只能有一条线程执行这个方法,所以效率低
    public static synchronized Test getInstance(){

    //这里不是加锁了吗,为什么说线程不安全
    //上面说过多个线程可以同时进入if内,这里也是一样,线程a先获取锁,先执行instance = new Test();
    //然后程b获取锁又执行了一次instance = new Test();
    //这里你有没有要把if的判断放到synchronized{}内部的想法,下面这种就是你所想的。
       if(instance == null){
       		synchronized(Test.class){
       			instance = new Test();
			}
	   }
       return instance ;
    }
}

(双重校验锁)线程安全,推荐用

public class Test{

    private static Test instance = null;

	private Test(){}
	
	//对getInstance()方法加锁,因为同时只能有一条线程执行这个方法,所以效率低
    public static synchronized Test getInstance(){
    
       //这里其实只要synchronized内部的if判断就够了,为啥外部还要再放一个?
       //个人认为是:instance对象已经存在了的情况下,避免其他进程获取锁,因为这会造成线程的调度
       //(也称上下文切换)记住这句话,多线程对锁资源的竞争会引起上下文切换,锁竞争导致的
       //线程阻塞越多,上下文切换就越频繁,系统的性能开销就越大(不理解的话那就六个字:影响系统性能)
       if(instance == null){
       		synchronized(Test.class){
       			if(instance == null){
       				instance = new Test();//这里会有指令重排序,所以还可以改进为用volatile修饰
       				                      //private static volatile Test instance = null;
       			}
			}
	   }
       return instance ;
    }
}

(内部类)线程安全,推荐用

public class Test{

	private Test(){}
	
	//在Test类被装载时,内部类TestHolder不会被实例化,只有在调用getInstance()方法时才会。
	//当多个线程调用getInstance()方法时,为什么这里不会线程安全的问题?
	//类加载机制保证初始化线程时只有一个线程,本人能力有限,类加载机制自行百度
    private static class TestHolder(){
		private static Test instance = new Test();
	}	
	
    public static Test getInstance(){
       return TestHolder.instance ;
    }
}

(枚举)线程安全,极推荐用

//为啥枚举可以实现线程安全和单例呢
public enum SingletonEnum {
    instance;
    
    private SingletonEnum(){}
    
    public static void method(){//方法要是静态方法
        System.out.println("被调用的方法体");
    }
}

jdk1.5的时候引入了枚举类型,下面是jdk1.5之前的写法,枚举的实现原理和下面一致,只是使用了一些语法糖简化了书写

public class SingletonEnum {
 
         public static final SingletonEnum instance = new SingletonEnum ();
         
         //2、构造方法私有化
         private SingletonEnum(){}
}

Java 单例模式案例(Runtime,)

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
    
    private Runtime() {}
}

答疑解惑

提问1:外部类可以通过类名.静态属性,和类名.静态方法的方式进行访问,为什么不把实例对象test 定义为public,变成下面这种方式?

public class Test{
	//将 private 变为 public
    public static Test test = new Test();

	private Test(){}
	
    //public static Test getInstance(){
    //   return test;
    //}
}

答:咦,这个是不是跟我们刚刚看过的枚举在jdk1.5之前的写法类似啊,稍有不同的是少了个final关键词,这个有什么影响呢。final 修饰的变量是不能修改的,这就意味着我们的test对象是可以被修改的,构造方法都被私有化了,还怎么创建一个Test对象来修改呢,Java里创建对象的方式有四种,new关键字只是其中之一,另外三种是:反射,反序列化,克隆。我还想到一种就是Test的子类,可以把子类对象赋给父类的引用,利用多态的特性向上转型了,不过这种的话,不是导致Test对象有多个,而是获取的对象已经被更改了(对象地址不同)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值