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

编程入门 行业动态 更新时间:2024-10-10 09:17:47

单例<a href=https://www.elefans.com/category/jswz/34/1771241.html style=模式的实现及怎么访问一个单例模式"/>

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

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

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

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

举个栗子:
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;}
}

更多推荐

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

本文发布于:2024-02-13 20:32:49,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1760390.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!