单例模式的实现及怎么访问一个单例模式

一、单例模式怎么被访问:

首先,将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。
直白的讲就是,你不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象。

程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名.方法名()”;

举个栗子:
Singleton singleton = Singleton.getInstance();

使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。

这就是单例模式唯一实例必须设置为静态的原因

二、单例模式的实现

2.1 预加载

pubilic class	 PreloadSingleton{
//私有的构造方法,防止被其他类创建本类的一个对象,用该对象去调用类中方法
 private PreloadSingleton() {
   }
 private static PreloadSingleton singleton = new PreloadSingleton();
 //为使其他类可以访问本单例模式而写的方法
 public static PreloadSingleton getInstance() {
          return instance;
   }       
}

预加载的劣势:很明显的,没有使用该单例对象,该对象也会被加载到内存,这会造成内存的浪费。

2.2 懒加载

public  class Singleton{
//私有构造方法
private Singleton() {
   }
private static  Singleton singleton = null;
//用于外部访问的静态方法
public static Singleton getInstance{
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}

三、单例模式和线程安全

(1)预加载只有一条语句return instance,这显然可以保证线程安全。但是,我们知道预加载会造成内存的浪费。

(2)懒加载不浪费内存,但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。

3.1 保证懒加载的线程安全

首先想到的是可以使用 synchronized关键字来保证线程安全(使用lock也可以的)

public class Singleton {
  private Singleton() {
   }
  private static Singleton instance = null;
 // 解决了线程安全问题,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗
  public static synchronized Singleton getInstance() {
          if (instance == null) {
                 instance = new Singleton();
          }
          return instance;
   }
}

使用synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。(运行效率低
所以我们可以把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁

public class Singleton{
 private Singleton() {
   }
      private static Singleton instance = null;
       public static  Singleton getInstance() {
       //双重检查模式,外层的判断是为了提高效率(只需要同步一次),里层的判断就是第一次实例化需要(只需要实例化一次)(多线程的原因,可能有多个线程同时到这里来,假如第一个线程实例化之后,后面的线程就会因为这个判断不再新建实例了)。)
          if (instance == null) {
                 synchronized (Singleton.class) {
                       if (instance == null) {
                              instance = new Singleton();
                       }
                 }
          }
          return instance;
   }
}

当 instance 为 null 时,两个线程可以并发地进入 if 语句内部。然后,一个线程进入 synchronized 块来初始化 instance,而另一个线程则被阻断。当第一个线程退出 synchronized 块时,等待着的线程进入并创建另一个 Singleton 对象。注意:当第二个线程进入 synchronized 块时,它并没有检查 instance 是否非 null。

线程安全主要体现在以下三个方面:

原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作

可见性:一个线程对主内存的修改可以及时的被其他线程观察到

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

不满足原子性或者顺序性,线程肯定是不安全的,这是基本的常识,不再赘述。我主要讲一下为什么new Singleton()无法保证顺序性。
我们知道创建一个对象分三步:

memory=allocate();//1:初始化内存空间

ctorInstance(memory);//2:初始化对象

instance=memory();//3:设置instance指向刚分配的内存地址

jvm为了提高程序执行性能,会对没有依赖关系的代码进行重排序,上面2和3行代码可能被重新排序,也就是可能会有还没初始化对象就设置instance指向内存空间,此时再有一个线程来访问会发现 singleton对象不为null,然后引用了(实际上还是null),导致线程不安全。因为new一个对象的代码是无法保证顺序性的,所以,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。

volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。其一个作用就是可以禁止指令的重排优化。

最终的代码应该是这样的:

public class  Singleton{
private Singleton(){
}
private static volatile Singleton singleton = null;
public  static Singleton getInstance{
	if(singleton == null){
		synchronized (Singleton.class) {
		if(singleton == null){
  	    singleton = new Singleton();
						}
					}
				}
return singleton;
		}
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值