您当前的位置: 首页 >  Java

止步前行

暂无认证

  • 2浏览

    0关注

    247博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java设计模式——单例模式【Single Pattern】(七种方式)

止步前行 发布时间:2019-07-14 22:59:14 ,浏览量:2

一、引言

在《Java编程思想》的第六章,介绍访问权限控制关键字private时,引出了单例模式,但书中并没有详细的说明,只是一带而过。关于private访问控制符,书中这样描述:“关键字private的意思是,除了包含该成员的类之外,其他任何类都无法访问这个成员”。

二、创建单例模式的七种方式 1、饿汉式(我饿,我比较着急,所以一开始就把对象创建出来)

单例模式的原理就是基于上面那句话,具体的做法是:将类的构造函数设置为private,这样,除了类自己的方法,无论外部其他类,都无法生成该类的对象,这样就可以控制该类生成的对象。单列模式能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。

懒汉式和饿汉式的区别就是创建对象的时机。

单例模式的代码如下(饿汉式,我非常饿,比较急):

// 饿汉式(静态变量)
public class SingletonObject1 {

    private static final SingletonObject1 instance = new SingletonObject1();

    private SingletonObject1() {
    }

    public static SingletonObject1 getInstance() {
        return instance;
    }
}

// 饿汉式(静态代码块)
class Singleton {
	
	//1. 构造器私有化, 外部能new
	private Singleton() {		
	}
	
	//2.本类内部创建对象实例
	private  static Singleton instance;
	
	static { // 在静态代码块中,创建单例对象
		instance = new Singleton();
	}
	
	//3. 提供一个公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		return instance;
	}
	
}

优缺点说明:

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。 缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到懒加载的效果

结论:这种单例模式可用,但可能造成内存浪费

2、懒汉式(懒汉式:比较懒,用到的时候,我再创建对象)
public class SingletonObject2 {

    private static SingletonObject2 instance;

    private SingletonObject2() {
    
    }

    public static SingletonObject2 getInstance() {
        if (null == instance)
            instance = new SingletonObject2();
        return SingletonObject2.instance;
    }
}

优缺点说明:

(1)、 起到了懒加载的效果,但是只能在单线程下使用。 (2)、 如果在多线程下,一个线程进入了if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

结论:在实际开发中,不要使用这种方式

3、懒汉式加锁
public class SingletonObject3 {
    private static SingletonObject3 instance;

    private SingletonObject3() {

    }

    public synchronized static SingletonObject3 getInstance() {
        if (null == instance)
            instance = new SingletonObject3();
        return SingletonObject3.instance;
    }
}

优缺点说明: (1)、解决了线程不安全问题 (2)、 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低 结论:在实际开发中,不推荐使用这种方式

4、Double-Check
public class SingletonObject4 {

    private static SingletonObject4 instance;

    private SingletonObject4() {
    }
    //double check
    public static SingletonObject4 getInstance() {

        if (null == instance) {
            synchronized (SingletonObject4.class) {
                if (null == instance)
                    instance = new SingletonObject4();
            }
        }
        return SingletonObject4.instance;
    }
}

缺点:这种方式,会发生空指针异常。比如,当线程一获取锁之后,正在创建对象(创建对象需要一系列步骤,编译器优化会发生指令重排序),当线程一创建对象后,但对象的一些属性并没有初始化结束。此时线程二到了条件判断,此时对象并不为空,但是对象的一些属性的初始化并没有结束,如果此时用线程二去访问该线程的一些属性,可能会造成空指针异常。解决这个问题,我们可以把对象定义成 volatile 。volatile 的作用是禁止JVM将指令重排序。

5、Double-Check 加 volatile
public class SingletonObject5 {

    private static volatile SingletonObject5 instance;

    private SingletonObject5() {
        //
    }

    //double check add volatile
    public static SingletonObject5 getInstance() {

        if (null == instance) {
            synchronized (SingletonObject4.class) {
                if (null == instance)
                    instance = new SingletonObject5();
            }
        }
        return SingletonObject5.instance;
    }
}

到了这一步,对象终于可以安全的创建了,也不会有什么问题了。优缺点说明: (1)、 Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。

(2)、这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步。 (3)、线程安全;延迟加载;效率较高

结论:在实际开发中,推荐使用这种单例设计模式

6、静态内部类 (推荐)
public class SingletonObject6 {

    private SingletonObject6() {

    }

    private static class InstanceHolder {
        private final static SingletonObject6 instance = new SingletonObject6();
    }

    public static SingletonObject6 getInstance() {
        return InstanceHolder.instance;
    }
}

优缺点说明: (1)、这种方式采用了类装载的机制来保证初始化实例时只有一个线程。 (2)、静态内部类方式在Singleton类被装载时,内部类并不会立即实例化,而是在调用getInstance方法,才会装载内部类类,而且只调用一次,从而完成Singleton的实例化。 (3)、类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。 (4)、优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高 结论:推荐使用

7、枚举 (推荐)
public class SingletonObject7 {
    private SingletonObject7() {

    }

    private enum Singleton {
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton() {
            instance = new SingletonObject7();
        }

        public SingletonObject7 getInstance() {
            return instance;
        }
    }

    public static SingletonObject7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
}
三、总结

总结一下,单例模式中,公用方法getInstance()方法都是静态的(static),实例和构造方法又都是私有的(private)。但是饿汉式每次调用的时候不用做创建,直接返回已经创建好的实例。这样虽然节省了时间,但是却占用了空间,实例本身为static的,会一直在内存中带着。懒汉式则是判断,在用的时候才加载,会影响程序的速度。最关键的是,在并发的情况下,懒汉式是不安全的。

其他种方式,也分别说了可能存在的缺点,推荐使最后两种。

单例模式使用的场景:

需要频繁进行创建和销毁对象、创建对象时耗过多或耗费资源过多(即:重量级对象 ),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象 (比如数据源、session工厂 等)

关注
打赏
1657848381
查看更多评论
立即登录/注册

微信扫码登录

0.0412s