设计模式之——单例模式

引言

  设计模式分为三种类型:

  1)创建者模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

  2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

  3)行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)

一、单例模式

  所谓的单例模式,就是采取一定的方法保证在整个软件系统中,对于某个类只能存在一个实例对象,并且该类只对外提供一个获取该对象的方法(静态方法)

  单例模式有七种方式:

  1)饿汉式(静态常量)

  2)饿汉式(静态代码块)

  3)懒汉式(线程不安全)

  4)懒汉式(线程安全,同步方法)

  5)双重检查

  6)静态内部类

  7)枚举

 1)饿汉式(静态常量)【主】

public class singleton1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}
class Singleton{
    private Singleton(){}//构造器私有化
    private final static Singleton singleton= new Singleton();
    public static Singleton getInstance() {
        return singleton;
    }
}

  这种方式在类初始化时就会完成 “单例对象” 的实例化,避免了线程安全问题。但同时这也是他的缺点,假如此类还有其他静态方法和属性,可能调用方调用的并不是getInstance方法,并不会使用此 “单例对象”,但是调用了类的其他静态方法非final成员势必导致类的初始化,而一旦类初始化,就会初始化此 “单例对象”,这会造成一定的空间浪费,没有达到懒加载的效果。

 2)饿汉式(静态代码块)【次】

public class singleton2 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}
class Singleton{
    private Singleton(){}
    private final static Singleton singleton;
  //final修饰的变量可以在静态代码块、代码块和构造器中赋值
static { singleton = new Singleton(); } public static Singleton getInstance() { return singleton; } }

 3)懒汉式(线程不安全)【次】


public class singleton1 {
public static void main(String[] args) {
  for(int i=0;i<20;i++) {
    new Thread(() -> {
      Singleton singleton2 = Singleton.getInstance();
      System.out.println(singleton2);
    },"Thread"+i).start();
    }
  }
}
class Singleton{
  private Singleton(){}
  private static Singleton singleton;
  public static Singleton getInstance() {
  if(singleton == null)
    return singleton = new Singleton();
    return singleton;
  }
}

 

  这样的方式是线程不安全的,假设现在有两个线程A、B,线程A在进入 if(singleton == null)后并没有来得及创建对象,CPU就将资源转给了线程B,因为线程A此前并没有将类成员singleton实例化,所以线程B进入了if后创建了对象并返回,此时A线程拿到资源后加载上下文继续创建了对象。由此可知在多线程的情况下这种方式并不安全。所以在实际开发中不要使用这种方式。

com.qlu.singleton.Singleton@10d49a2
com.qlu.singleton.Singleton@10d49a2
com.qlu.singleton.Singleton@1fae4985
com.qlu.singleton.Singleton@10d49a2
.
.
.

 4)懒汉式(线程安全,同步方法)【主】

public class singleton1 {
    public static void main(String[] args) {
        for(int i=0;i<20;i++) {
            new Thread(() -> {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println(singleton2);
            },"Thread"+i).start();
        }
    }
}
class Singleton{
    private Singleton(){}
    private static Singleton singleton;
    public static synchronized Singleton getInstance() {//加锁
        while(singleton == null)
            return singleton = new Singleton();
        return singleton;
    }
}

  但是这样效率比较低,不推荐使用。

 5)双重检查

public class singleton1 {
    public static void main(String[] args) {
        for(int i=0;i<20;i++) {
            new Thread(() -> {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println(singleton2);
            },"Thread"+i).start();
        }
    }
}
class Singleton{
    private Singleton(){}
    private static volatile Singleton singleton;
    public static synchronized Singleton getInstance() {
        if(singleton == null) {
            synchronized (Singleton.class) {
                if(singleton == null) {
                    return singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

  这种方式是线程安全的,假设现在有两个线程A、B,线程A在进入 if(singleton == null)后并没有来得及创建对象,CPU就将资源转给了线程B,因为线程A此前并没有将类成员singleton实例化,所以线程B进入了if后创建了对象并返回,此时A线程拿到资源后加载上下文开始if判断,由于B已经创建对象,所以并不能执行if里面的代码。由此可知在多线程的情况下这种方式是安全的,所以在实际开发中推荐使用这种方式。

 6)静态内部类【推荐使用】

public class singleton1 {
    public static void main(String[] args) {
        for(int i=0;i<20;i++) {
            new Thread(() -> {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println(singleton2);
            },"Thread"+i).start();
        }
    }
}
class Singleton{
    private Singleton(){}
    
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

 7)枚举

public class singleton1 {
    public static void main(String[] args) {
        for(int i=0;i<20;i++) {
            new Thread(() -> {
                Singleton singleton2 = Singleton.INSTANCE;
                singleton2.say();
            },"Thread"+i).start();
        }
    }
}
enum Singleton{
    INSTANCE;
    public void say() {
        System.out.println("hello");
    }
}

二、JDK中使用的单例模式

 饿汉式的RunTime:

 

三、单例模式的应用场景

 1)单例模式的应用变量为什么要倍设计成static修饰呢?类本身多外暴露的接口时static的

 2)单例模式保证了系统内存(方法区)中该类只要一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统的性能,对于一些经常使用到的 “重量级” 的对象、工具类对象、频繁访问数据库或文件系统的对象,使用单例模式也是很好的选择。

原文地址:https://www.cnblogs.com/superlsj/p/11699935.html