JAVA单例模式

JAVA单例设计模式

    • 1、饿汉单例模式
    • 2、懒汉单例模式-线程不安全
    • 3、懒汉单例模式-加锁
    • 4、懒汉单例模式-双重校验
    • 5、懒汉单例模式-静态内部类
    • 6、枚举单例

关于单例实际应用场景就不多说了,工作中也是经常用到,可以说非常实用,我们直接看比较常见的几种单例模式。

1、饿汉单例模式

package com.example.chuan.eatApple.singleton.test1;

//饿汉单例,线程安全
public class SingleTon {
    //私有化构造器,使得外部不可以实例化对象
    private SingleTon(){}
    private static SingleTon instance=new SingleTon();
    //只能通过getInstance静态方法获得对象
    public static  SingleTon getInstance(){
        return instance;
    }
}

测试一下:

package com.example.chuan.eatApple.singleton.test1;

public class test {
    public static void main(String[] args) {
        SingleTon singleTon1=SingleTon.getInstance();
        SingleTon singleTon2=SingleTon.getInstance();
        System.out.println(singleTon1==singleTon2);
    }
}

使用==进行比较,结果为true,说明两个引用指向的是同一个对象!饿汉单例就是在加载类的时候直接初始化SingleTon,不管最终会不会使用到,都会实例化一个对象,所以才叫饿汉,这样子明显有个缺点就是,使用不到的时候会造成资源浪费。

2、懒汉单例模式-线程不安全

package com.example.chuan.eatApple.singleton.test3;

//懒汉单例,线程不安全,不推荐使用
public class SingleTon {
    private SingleTon(){}

    private static SingleTon instance;
    public static SingleTon getInstance(){
        /*
        * 这里存在线程安全问题,假设两个线程同时到达
        * 线程1经过判断为空之后刚好让出了cpu时间片
        * 线程2也通过了判断,最后的结果就是new了
        * 两个不同的对象,所以说这个单例模式是
        * 线程不安全的*/
        if (instance==null){
            instance=new SingleTon();
        }
        return instance;
    }
}

可以看到instance成员变量一开始是没有初始化的,而是调用了getInsatnce方法之后才会初始化,所以实现了懒加载,不用就不会new对象,也就不会造成浪费,但是这个案例明显是线程不安全的,有同学想到了,那我们加锁不就行了?我们来看看加锁会怎么样。

3、懒汉单例模式-加锁

 package com.example.chuan.eatApple.singleton.test6;
 //懒汉单例
 public class SingleTon {
    private SingleTon(){}
    private static SingleTon instance;
    //方法上直接加锁
    public synchronized static SingleTon getInstance(){
            if (instance == null) {
               instance = new SingleTon();
           }
      return instance;
   }
}

可以看到,在getInstance方法上直接加锁,保证每次只有一个线程能调用,确实确保了懒加载+单例+线程安全,但是有没有缺点呢?有的小伙伴已经想到了,我们的案例中确实只有几行代码,不会造成性能上的损耗,但是实际应用中呢,我们的getInstance上或许还有很多行代码,锁住整个方法而不让其他线程进来,必定会造成速度的损失,而我们实际想做的只是在new对象上上锁保证单例,而不是锁住整个方法。有小伙伴就举手了,我懂我懂,那就在上面的示例中的第9行上锁:

 public  static SingleTon getInstance(){
 			//错误示范
            if (instance == null) {
                synchronized(SingleTon.class) {
                    instance = new SingleTon();
                }
            }
        return instance;
    }

我们仔细想想,这样能保证线程安全吗?其实仔细比较,这样子做跟前面的第2条并没有什么区别,是线程不安全的!好了废话少说,我们看重点,下面的方法才是强烈推荐使用的单例模式!

4、懒汉单例模式-双重校验

package com.example.chuan.eatApple.singleton.test2;

//懒汉单例-双重校验实现,线程安全,推荐使用
public class SingleTon {
  private SingleTon(){}
  private static SingleTon instance;
  public static SingleTon getInstance(){
      //细品一下如果两个线程同时通过会怎么样
      if (instance==null){
          synchronized (SingleTon.class) {
              if (instance == null) {
                  instance = new SingleTon();
              }
          }
      }
      return instance;
  }
}

看透之后是不是会感慨一声:精彩!很多时候前辈发明的东西,跟体育竞技没什么两样,都是为了更高更快更强。

5、懒汉单例模式-静态内部类

package com.example.chuan.eatApple.singleton.test4;

//懒汉单例-静态内部类实现,线程安全,推荐使用
public class SingleTon {
    private SingleTon(){}

    private static class SingleTonHelper{
        //通过类加载的特点实现单例
        private static SingleTon instance=new SingleTon();
    }
    public static SingleTon getInstance(){
        //通过内部类实现懒加载,只有第一次调用时才开始加载
        return SingleTonHelper.instance;
    }
}

这里又考察一个知识点了,内部类是什么时候加载的?该回去好好补补java内部类的知识啦(内部类是延时加载的,只在第一次调用时候加载,不使用就不加载)

6、枚举单例

利用枚举的特性,可以快速有效的实现单例!而且严格意义上来说,枚举才是真正的单例,其他单例模式都会被java特有的反射机制破坏。

//枚举单例
public enum SingleTon {
    SINGLE_TON;
    public void method(){
        System.out.println("枚举单例");
    }
}

总结:
一下在列出那么多个单例,我们到底要使用哪个呢?我们当然择优选择啦,根据自己的喜好和实际业务进行选择,当然这里肯定优先推荐1、4、5、6这几种,第一种方法也是非常简单而且方便使用,在确定不会造成浪费的情况下,当然是直接一通乱敲最简洁(其实小声哔哔说句实话,又能浪费多少呢?)- -|||说好的更高更快更强呢?

你可能感兴趣的:(java,设计模式,面试,编程语言,spring)