模式的实现及怎么访问一个单例模式"/>
单例模式的实现及怎么访问一个单例模式
一、单例模式怎么被访问:
首先,将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。
直白的讲就是,你不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象。
程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名.方法名()”;
举个栗子:
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;}
}
更多推荐
单例模式的实现及怎么访问一个单例模式
发布评论