单例模式(饿汉式、懒汉式)

单例设计模式分类两种:

  1. 饿汉式:类加载就会导致该单实例对象被创建
  2. 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建


饿汉式的两种写法:

方式一:

/*
    单例:饿汉式
 */
public class Singleton {
    //无参构造私有化
    private Singleton() { };

    private static Singleton intence = new Singleton();

    public static Singleton genIntence(){
        return  intence;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Singleton singleton = Singleton.genIntence();
        Singleton singleton2 = Singleton.genIntence();
        System.out.println(singleton==singleton2);

    }
}

结果为:true (==判断的是对象在内容中的地址值是否相同)

解释:
  该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象 intence 对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

方式二:

/*
    单例:饿汉式,静态代码块方式
 */
public class Singleton {
    private Singleton(){};
    private static Singleton singleton;
    static {
        singleton = new Singleton();
    }
    public static Singleton genIntence(){
        return singleton;
    }
}

测试:

public class Test {
    public static void main(String [] args){
        Singleton singleton = Singleton.genIntence();
        Singleton singleton2 = Singleton.genIntence();
        System.out.println(singleton==singleton2);
    }
}

结果为:true (==判断的是对象在内容中的地址值是否相同)

解释:
 在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式一基本相同,该方式也存在内存浪费问题。


懒汉式的四种写法:

线程不安全方式一:

 多线程下会产生线程安全问题

public class Singleton{
	private Singleton(){};
	private static Singleton singleton;  //只是声明一个该类型的变量,并没有进行赋值
	//对外提供访问方式
	public static Singleton getInstance(){
		/*
			判断singleton 是否为null,如果为null,说明还没有创建Singleton类的对象,
			进入if里边如果为null创建一个并返回,如果有直接返回。
		*/
		if(singleton ==null){
		    //假设线程1进入后隔着等待了,此时线程2进入后立刻获取到cpu的执行权,也会进入到该判断里边
			singleton=new Singleton();
		}
		return instance;
	}
}

测试:

public class Test {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1==instance2);
    }
}

执行结果:true


线程安全方式二:

 给对外提供的静态方法加 synchronized 保证多线程情况下每个线程执行完后释放CPU的执行权后轮流执行。

public class Singleton{
	private Singleton(){};
	private static Singleton singleton;  //只是声明一个该类型的变量,并没有进行赋值
	//对外提供访问方式
	public static synchronized Singleton getInstance(){
		/*
			判断singleton 是否为null,如果为null,说明还没有创建Singleton类的对象,
			进入if里边如果为null创建一个并返回,如果有直接返回。
		*/
		if(singleton ==null){
			singleton=new Singleton();
		}
		return instance;
	}
}

测试:

public class Test {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1==instance2);
    }
}

执行结果:true


双重检查锁方式三: 又称:Double check lock
  在上边的懒汉式线程安全方式中,对于getInstance()方法来说,绝大部分操作都是读操作,读操作就是第一次创建对象赋值后,在进行第二次判断就直接有对象了,singleton 不为null就直接返回了。读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法。此时就需要调整加锁的时机。因而由此产生了一种新得实现模式也就是所说得:双重检查锁模式

/*
    双重检查锁方式
 */
public class Singleton {

    private Singleton(){};
    
    private static volatile Singleton singleton;

    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        //第一次判断,如果singleton不为null,不进入枪锁阶段,直接返回
        if (singleton==null){
            singleton=new Singleton();
            synchronized (Singleton.class){
                //抢到锁之后再次判断是否为空
                if (singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1==instance2);
    }
}

结果为:true

解释:
   双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题、上面的双重检查锁模式看上去完美无缺,其实是存在一些问题的。在多线程情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
   要解决双重检查锁模式带来空指针遗产过的问题,只要使用Volatile 关键字,Volatile 关键字可以保证可见性和有序性。

总结:
添加Volatile 关键字之后的双重检查锁模式是一种比较好的单例设计模式,能够保证在多线程情况下线程安全也不会有性能问题。


静态内部类方式:

  • 静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性、方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。

/*
    单例懒汉式之静态内部类方式
 */
public class Singleton {
    //私有构造
    private Singleton singleton;

    //静态内部类
    private static class SingletonHolder{
        private static final Singleton SINGLETON =new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.SINGLETON;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

结果为:true

解释:
 第一次加载Singleton类时不会去初始化 SINGLETON ,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化 SINGLETON,这样不仅能确保线程安全,也能保证 Singleton类的唯一性。

总结:
静态内部类单例模式是一种优秀的单例模式,是项目中比较常用的一种模式,在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值