Java设计模式之-单例模式
- 概念
- 饿汉式
- 懒汉式
- 懒汉式单例模式的问题
概念
保证一个类仅有一个实例,并提供一个全局访问方法。构造方法私有化,类内部产生实例化对象并提供static方法获取实例化对象,不管外部怎么操作,都只有一个实例化对象。
通俗点讲单例模式就是:类的构造函数弄成private ,即不想让别人用new 方法来创建多个对象,可以在类里面先生成一个对象,然后写一个public static方法把这个对象return出去。
饿汉式
在系统加载类的时候会自动提供实例化对象。常见代码如下:
/**
* 单例模式-饿汉式
*/
public class Singleton {
//私有静态方法,防止被引用。为了使得类内部不能再次实例化,使用final
private static final Singleton instance=new Singleton();
//构造方法私有化,防止被实例化
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
public void print(){
System.out.println("Hello world");
}
}
/**
* 调用测试
*/
public class test {
public static void main(String[] args) {
Singleton instance=null;//声明对象
instance=Singleton.getInstance();
instance.print();
}
}
注意:为什么使用static?在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。一句话:方便在没有创建对象的情况下来进行调用。
懒汉式
在系统加载类的时候不会自动提供实例化对象,而是在第一次使用的时候,进行实例化对象处理。常见代码如下:
/**
* 单例模式-懒汉式
*/
public class Singleton2 {
//私有静态方法,防止被引用,此处赋值为null,目的是实现延迟加载
private static Singleton instance=null;
//构造方法私有化,防止被实例化
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){//第一次使用时才实例化对象
instance=new Singleton();
}
return instance;
}
public void print(){
System.out.println("Hello world");
}
}
为什么需要单例模式?:如果一个类不需要产生重复对象,例如:一个类启动只需要一个类负责保存程序加载的数据信息。保证不管谁访问,都只产生一个对象。
反射与懒汉式单例模式
如果在多线程情况下,如上懒汉式单例模式代码就会造成不只产生一个实例化对象,这时候单例模式就不起作用了。这个问题的原因是多线程情况下,以下判断不同步,造成instance判断都为空,所以会产生不只一个实例化对象。
if(instance==null){//第一次使用时才实例化对象
instance=new Singleton();
}
针对此问题,则需要进行同步处理,同步自然会想到synchronized关键字。修改如下所示:
//需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
这个时候确实实现了同步,但是效率比较低,代价比较大。因为代码里面只有一个地方需要同步处理:instance实例化对象处理部分,在以上修改直接添加synchronized 显得草率了,所以这里需要更加合理的进行同步处理。
在懒汉式实现单例模式的代码中,有使用synchronized关键字来同步获取实例,保证单例的唯一性,但是上面的代码在每一次执行时都要进行同步和判断,无疑会拖慢速度,使用双重加锁机制正好可以解决这个问题,修改后整个代码如下所示。
/**
* 单例模式-懒汉式
*/
public class Singleton {
//私有静态方法,防止被引用,此处赋值为null,目的是实现延迟加载
private static volatile Singleton instance=null;
//构造方法私有化,防止被实例化
private Singleton(){
}
public static Singleton getInstance(){
//需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
if(instance==null){//第一次使用时才实例化对象
synchronized(Singleton.class){//反射机制
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
public void print(){
System.out.println("Hello world");
}
}
双重加锁机制,这里的双重指的的双重判断,而加锁单指那个synchronized。为什么要进行双重判断,其实很简单,第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例。至于第二个判断,有点查遗补漏的意味在内。
关于锁内部的第二重空判断的作用,当多个线程一起到达锁位置时,进行锁竞争,其中一个线程获取锁,如果是第一次进入则dl为null,会进行单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。不论如何,使用了双重加锁机制后,程序的执行速度有了显著提升,不必每次都同步加锁。
volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,从而确保多个线程能正确的处理该变量。该关键字可能会屏蔽掉虚拟机中的一些代码优化,所以其运行效率可能不是很高,所以,一般情况下,并不建议使用双重加锁机制,酌情使用。