Java中多线程单例模式线程安全的写法

本文介绍了单例模式的基本概念,探讨了饿汉模式(实例化早)和懒汉模式(延迟实例化)的区别,以及它们在多线程环境下的线程安全问题,最后提到了加锁优化的解决方案。
摘要由CSDN通过智能技术生成

目录

单例模式的概念

饿汉模式

懒汉模式


单例模式的概念

  单例模式是多线程代码中一种经典的设计模式,设计模式类似于棋谱,下棋按照固定的套路来下,基本上棋力就不会太差。同样,设计模式就是把编程中的各种经典问题场景给你盘一盘,并且给你提供一些解决方案。遇到这个场景,代码就这么写,你的代码就不会写的很差。熟练掌握设计模式,不能提高你代码的上限,但是能保住你代码的下限

  设计模式有很多种,我们今天讲的是属于比较简单的单例模式

  顾名思义,单例模式指的就是单个实例,整个进程中的某个类,有且只有一个对象(不会new出来多个对象)。

  那么如何保证这个类只有一个对象呢?靠程序员口头保证可不行

  我们需要让编译器来帮助我们做一个强制的检查。通过一些编码上的技巧,使编译器可以自动发现咱们代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错

  从根本上保证对象是唯一实例,这样的代码就称为单例模式。

  单例模式有很多种不同的写法,我们这里主要介绍两种。

饿汉模式

  下面就是一段饿汉模式的代码:

 //单例
 //饿汉模式,创建时机非常早
class  Singleton{
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return  instance;
    }
    private Singleton(){

    }

 }
public class ThreadDemo12 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

    }
}

我们注意,static成员初始化时机是在类加载的时候,static修饰的其实是“类属性”,就是在“类对象”上的,每个类的类对象在JVM中只有一个,里面的静态成员,只有一份。所以对象的初始化也是只执行一次的。

后续需要使用这个类的实例,就可以通过getInstance来获取已经new好的这个,而不是重新new。

类之外的代码,尝试new的时候,势必就要调用构造方法,由于构造方法是私有的,无法调用,就会编译出错。

懒汉模式

  懒汉模式,不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建。(如果不使用了,就会把创建实例的代价就节省下来了)

下面是一段懒汉模式代码:

//懒汉模式  不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建
class  SingletonLazy{
      private static volatile SingletonLazy instance = null;
      public static SingletonLazy getInstance(){
          Object locker = new Object();
                  if(instance == null){
                      instance = new SingletonLazy();
          }
          return instance;
      }
      private SingletonLazy(){

      }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1);
        System.out.println(s2);

    }
}

添加if判断的目的就是,啥时候调用,就啥时候创建。如果不调用,就不创建了。

如果代码中存在多个单例类,使用饿汉模式,就会导致这些实例都是在程序启动的时候扎堆创建的,可能把程序启动时间拖慢

如果是懒汉模式,啥时候首次调用,调用时机是分散的,化整为零,用户不太容易感知到“卡顿”。

那懒汉模式是否有缺点呢?

当然,懒汉模式是线程不安全的,而饿汉模式是线程安全的。(重点)

饿汉模式的创建时机是在java进程启动(比main调用还早的时机),后续线程执行getInstance的时候,意味着上述实例早都已经有了。每个线程的getInstance只做了一件事,就是读取上述静态变量的值多个线程读取同一个变量,是线程安全的

而懒汉模式的代码,在多线程的环境下,就可能产生问题。

当t1,t2两个线程创建实例的时候,就可能出现以下执行顺序。

t2切换回来之后,要沿着之前的位置继续往下执行,就会直接进入条件内部再次执行new操作。创建了两个对象,不符合单例模式了。

这种情况下,可以通过加锁的方式,来使创建实例的操作原子化,避免上述情况。

//懒汉模式  不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建
class  SingletonLazy{
      private static volatile SingletonLazy instance = null;
      public static SingletonLazy getInstance(){
          Object locker = new Object();
          synchronized (locker) {
              if (instance == null) {
                  instance = new SingletonLazy();
              }
          }
          return instance;
      }
      private SingletonLazy(){

      }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1);
        System.out.println(s2);

    }
}

t1执行加锁之后,t2就会阻塞,直到t1释放锁(new完了),t2才能拿到锁,才能进行条件判定。t2的条件就会认为Instance非null,也就不会再new了。

当然,加锁本身也是有开销的。我们不一定每次创建实例都要加锁,那样也会使效率降低

代码可以增加如下改进:

//懒汉模式  不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建
class  SingletonLazy{
      private static volatile SingletonLazy instance = null;
      public static SingletonLazy getInstance(){
          Object locker = new Object();
          if(instance == null) {
              synchronized (locker) {
                  if (instance == null) {
                      instance = new SingletonLazy();
                  }
              }
          }
          return instance;
      }
      private SingletonLazy(){

      }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1);
        System.out.println(s2);

    }
}

最外面再加一层if的目的是:判定是否要加锁。如果是实例化之后,线程自然安全了,就无需加锁了。实例化之前,new之前,就应该要加锁。

第二层if的目的是:判定是否要创建对象

两个if之间,synchronized回使该线程阻塞,阻塞过程中其他线程就可能会修改Instance的值。

以上,关于单例模式,希望对你有所帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值