为什么说单例模式的饿汉式是线程安全的?

一、类加载的方式是按需加载,且只加载一次

因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。单例就是该类只能返回一个实例。

换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例。

也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。即饿汉式单例天生就是线程安全的。

二、单例模式几种实现

单例模式特点

私有构造方法private SingleTon1()
私有静态全局变量private static SingleTon1 singleton = new SingleTon1()
公有静态方法public static getInstance()

instance为什么一定要是static的?

1.通过静态的类方法(getInstance) 获取instance,该方法是静态方法,instance由该方法返回(被该方法使用),如果instance非静态,无法被getInstance调用;

2.instance需要在调用getInstance时候被初始化,只有static的成员才能在没有创建对象时进行初始化。且类的静态成员在类第一次被使用时初始化后就不会再被初始化,保证了单例;

3.static类型的instance存在静态存储区,每次调用时,都指向的同一个对象。其实存放在静态区中的是引用,而不是对象。而对象是存放在堆中的。

单例模式的构造方法为什么私有?

1.设置private以后,每次new对象的时候都要调用构造方法。而private的权限是当前类,那么其他类new对象的时候一定会失败。
2.设置成private是考虑封装性,防止在外部类中进行初始化,也就不是单例了。

饿汉式:

//基于JVM的类加载器机制避免了多线程的同步问题,对象在类装载时就实例化
public class SingleTon1(){
  private SingleTon1(){
  }
  private static SingleTon1 singleton = new SingleTon1();
  public static getInstance(){
     return singleton ;
  }
}

懒汉式:

//能够在getInstance()时再创建对象,所以称为懒汉式。
//这种实现最大的问题就是不支持多线程。因为没有加锁同步。
public class SingleTon2(){
   private SingleTon2(){
   }
   private static SingleTon2 singleton2 = null;
   public static getInstance(){
     if(singleton2 == null){
	   singleton2 = new SingleTon2();
	 }
	  return singleton2 ;
   }
}

加同步锁synchronized的懒汉模式:

//除第一次使用,getInstance()需要同步,后面getInstance()不需要同步;每次同步,效率很低。
public class SingleTon3(){
   private SingleTon3(){
   }
   private static SingleTon3 singleton3 = null;
   public synchronized static getInstance(){
     if(singleton3 == null){
	   singleton3 = new SingleTon3();
	 }
	  return singleton3 ;
   }
}

双重锁模式:

//安全且在多线程情况下能保持高性能。
//实例变量需要加volatile 关键字保证易变可见性
public class SingleTon4{
  private SingleTon4(){
  }
  private volatile static SingleTon4 singleton4 = null;
  public static SingleTon4 getSingleton(){
     if(singleton4 == null){
        synchronized (SingleTon4.class){
          if(singleton4 == null){
 		      singleton4 = new SingleTon3();
 		  }
		}
	 }
	  return singleton4 ;
  }
}

静态内部类模式:

//利用了JVM类加载机制来保证初始化实例对象时只有一个线程,
//静态内部类SingletonHolder类只有第一次调用getInstance方法时,才会装载从而实例化对象
public class Singleton5{
  private SingleTon5 (){
  }
  private static class SingletonHolder{
       private static final Singleton5 = new Singleton5();
  }
  public static final Singleton5 getInstance() {
       return SingletonHolder.Singleton5 ;
  }
} 

三、单例模式VS静态类(静态属性/方法)

把类中所有属性/方法定义成静态也可以实现"单例"。 静态类不用实例化就可以使用,虽然使用比较方便,但失去了面向对象的一些优点,适用于一些过程简单且固定、不需要扩展变化、不需要维护任何状态的类方法,如java.lang.Math,里面每种计算方法基本都是固定不变的。那为什么需要用"NEW"单例模式,而不把类中所有属性/方法定义成静态的?
单例模式保证一个类对象实例的唯一性,有面向对象的特性,虽然扩展不容易,但还是可以被继承(protected权限的构造方法)、重写方法等。

四、Java反射攻击破坏单例模式

给实例构造函数protected或private权限,可以通过相关反射方法,改变其权限,创建多个实例。比如:

public class Test {
     public static void main(String args[]) {
		    private Singleton() {};
            Singleton singleton = Singleton.getInstance();
        	try {
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton singletonnew = constructor.newInstance();
            System.out.println(singleton == singletonnew);
            //输出结果为 false
        } catch (Exception e) {
 
        }     
    }
}

解决方案:可以给构造函数加上判断限制创建多个实例,如下:

private Singleton() {
     if (null != Singleton.singleton) {
         throw new RuntimeException();
    }
}

五、单例模式中的单例对象会不会被垃圾回收

对于JDK1.2后的JVM HotSpot来说,判断对象可以回收需要经过可达性分析,由于单例对象被其类中的静态变量引用,所以JVM认为对象是可达的,不会被回收。
另外,对于JVM方法区回收,由堆中存在单例对象,所以单例类也不会被卸载,其静态变量引用也不会失效

六、多JVM/ClassLoader的系统使用单例类

不同ClassLoader加载同一个类,对类本身的对象(Singleton.class)来说是不一样的,所以可以创建出不同的单例对象,对不同JVM的情况更是如此,这些在JavaEE开发中还是比较常见。
所以,在多JVM/ClassLoader的系统使用单例类,需要注意单例对象的状态,最好使用无状态的单例类。

七、Spring(IOC框架)实现的单例

Spring的一个核心功能控制反转(IOC)或称依赖注入(DI):
高层模块通过接口编程,然后通过配置Spring的XML文件或注解来注入具体的实现类(Bean)。
这样的好处的很容易扩展,想要更换其他实现类时,只需要修改配置就可以了。通过IOC容器来实现,其默认生成的Bean是单例的(在整个应用中(一般只用一个IOC容器),只创建Bean的一个实例,多次注入同一具体类时都是注入同一个实例)

IOC容器来实现过程简述如下:
当需要注入Bean时,IOC容器首先解析配置找到具体类,然后判断其作用域(@Scope注解);
如果是默认的单例@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON),则查找容器中之前有没有为其创建了Bean实例;
如果有则直接注入该Bean实例,如果没有生成一个放到容器中保存(ConcurrentHashMap – map.put(bean_id, bean)),再注入。

注:其中解析配置查找具体类、生成Bean实例和注入过程都是通过Java反射机制实现的。

从上面可以了解到,Spring实现的单例和我们所说的单例设计模式不是一个概念:
前者是IOC容器通过Java反射机制实现,后者只是一种编程方法(套路)。
但总的来说,它们都可以实现“单例”。

参考如下:
https://blog.csdn.net/Ricky_Monarch/article/details/99407326
https://blog.csdn.net/tjiyu/article/details/76572617
https://blog.csdn.net/qq_36523667/article/details/79014324
https://blog.csdn.net/naerna/article/details/80498633
https://blog.csdn.net/zcw4237256/article/details/79670608

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值