Java单例模式初识,以及好处

单例模式是一个很经典的设计模式,在java中无处不在。比如spring中的bean注入,工具类的编写等。

但是在刚接触单例模式时候,我总对这个饱汉式和饿汉式的称呼理解不好。甚至自己也一度加入自己理解,创建新的定义,比如饥饿营销,传统销售。其实本来是很直观的概念,不用再增加新定义的词汇来理解。

目录

一、初识

二、好处


 

一、初识

1)提前创建对象

class A{  
    private A(){}
    private static A instance = new A();//这里更像传统销售产品,加载类时,先把商品(实例)准备好
    public static A getInstance(){
        return instance;//传统的销售模式,我已经准备好商品了,你需要,我直接给你。
    }
 }
!注意:该模式如果A类中的东西很多,那么创建A对象时所花时间更多(整个类的加载变慢了)

这里还可以用枚举来实现单例,推荐使用这种方法来实现单例模式,简单粗暴高效有没有。

public enum SingletonEnum {
    INSTANCE1("实例1",1),INSTANCE2("实例2",2);
    /**
     * 实际上INSTANCE1变量的默认缺省等于是:
     * public static final INSTANCE1 = new  SingletonEnum("实例1",1);
     * 所以,语法糖的味道你知道
    */
    SingletonEnum(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "SingletonEnum{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}
//测试类
public class Test {
    public static void main(String[] args){
        System.out.println(SingletonEnum.INSTANCE1);
        System.out.println(SingletonEnum.INSTANCE2.getName());
    }
}

 

2)需要时再创建对象

class A{  
    private A(){}
    private static A instance;    
    public static A getInstance(){//在加载类的时候不创建A对象,不生产B商品,在有人预定的时候,再去生产(创建A对象)
        if(instance == null){
            //买家1(线程1) 买家2(线程2)
            instance = new A();//这里就会有线程安全问题,如果买家1预定了,A对象还没生成完,买家2又开始预定,那么就会生成两个不同的A,破坏了单例。
        }
        return instance;
    }
 }
!!!注意:该模式存在线程安全问题
解决方式:添加synchronized关键字到静态方法,或者在if语句外,添加synchronized静态代码块,传入参数(A.class)

3)扩展,双重校验锁实现单例模式(线程安全

public class Singleton {


    private volatile static Singleton uniqueInstance;
//因为 new 这个指令是非原子操作,底层是分成几条指令来执行的,加上 volatile 是禁止指令重排,保证别的线程读到的时候一定是状态和引用正常的、一个完整的对象,防止其他线程看到的是对象还没有完全实例化的内容。

    private Singleton() {
    }


    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间

  2. 初始化 uniqueInstance

  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

 

二、好处

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 减轻 GC 压力,缩短 GC 停顿时间,使用单例模式由于 new 操作的次数减少,因而对系统内存的使用频率也会降低。
  • 节省了内存开销

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值