单例,顾名思义全局只能有一个实例对象。

基本原理就是

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

  1. 使构造方法私有化(不能随便的通过构造方法创建实例来保证单例);

  2. 有一个可以获取实例的静态方法(因为自己不可以创建实例,所以该方法必须是静态的,否则无法调用);

  3. 有一个静态私有的局部变量指向自己(私有是因为不可外部调用,静态是因为2中的静态方法需要引用)。

单例模式又有饿汉式和懒汉式之分

  按名字理解,饿汉式比较急,不管用没用到直接先创建一个实例,好处是快,在需要的时候直接用,坏处是占用资源,因为如果没有用到,它也创建好在那放着:

 1 //单例模式-饿汉式-线程安全
 2 public class Singleton{
 3     //私有内部类,需要时进行加载
 4     private static Singleton singleton = new Singleton();
 5     //构造方法私有化,不允许外部创建
 6     private Singleton(){}
 7     //获取实例的静态方法,直接调用即可
 8     public static Singleton getInstance(){
 9         return singleton;
10     }
11 }

  懒汉式比较懒,等到用的时候才会创建出一个实例,好处是不会浪费资源,坏处是初次调用有一个创建的时间:

 1 //单例模式-懒汉式-非线程安全
 2 public class Singleton{
 3     //私有内部类,需要时进行加载
 4     private static Singleton singleton;
 5     //构造方法私有化,不允许外部创建
 6     private Singleton(){}
 7     //获取实例的静态方法,直接调用即可
 8     public static Singleton getInstance(){
 9         //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
10         if(singleton == null){
11             singleton = new Singleton();
12         }
13         return singleton;
14     }
15 }

  饿汉式是线程安全的,因为它是在类加载时直接初始化了一个实例,多个线程同时调用也只是这一个实例;而懒汉式则是非线程安全的,因为它是调用的时候才会创建实例,这样就有一个问题,当多个线程假如同时到了判断为空的逻辑时,就会每个线程创建一个实例,这样就违背了单例的规则。

  为了保证线程安全我们可以使用synchronized关键字进行方法同步或代码块,建议只同步判断为空的代码块,因为如果同步方法同步的作用域会比较大,锁的粒度比较粗,效率会低。下面同步方法的代码已经注释掉,保留了同步代码块的形式,这样即可保证线程安全。

 1 //单例模式-懒汉式-线程安全
 2 public class Singleton{
 3     //私有内部类,需要时进行加载
 4     private static Singleton singleton;
 5     //构造方法私有化,不允许外部创建
 6     private Singleton(){}
 7     //获取实例的静态方法,直接调用即可
 8     /*public static synchronized Singleton getInstance(){
 9         //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
10         if(singleton == null){
11             singleton = new Singleton();
12         }
13         return singleton;
14     }*/
15 
16     //获取实例的静态方法,直接调用即可
17     public static Singleton getInstance(){
18         synchronized (Singleton.class) {
19             //判断实例是否为空,如果为空也就是第一次调用则创建一个实例对象
20             if (singleton == null) {
21                 singleton = new Singleton();
22             }
23         }
24         return singleton;
25     }
26 }

   但这样因为有同步代码块效率其实还是低,我们可以使用内部类的方式实现:

 1 //单例模式-懒汉式-线程安全
 2 public class Singleton{
 3     //私有内部类,需要时进行加载
 4     private static class Inner {
 5         private static Singleton singleton = new Singleton();
 6     }
 7     //构造方法私有化,不允许外部创建
 8     private Singleton(){}
 9     //获取实例的静态方法,直接调用即可
10     public static Singleton getInstance(){
11         return Inner.singleton;
12     }
13 }

   这样既保证了效率(因为是在使用时才进行的实例初始化),也保证了线程安全问题(同饿汉式相似,是在内部类加载时直接进行的初始化)。

  到这里单例模式基本已经介绍完,但是需要注意的是:单例模式如果使用反射的方式依然是可以创建多个实例的,因为反射可以拿到私有的构造方法,就可以自己创造实例,所以我们使用单例模式要禁止使用反射进行对象的创建。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄