设计模式之单例模式

概述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的特点

在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在。
构造器必须是私有的,外部类无法通过调用构造器方法创建该实例。
没有公开的set方法,外部类无法调用set方法创建该实例。
提供一个公开的get方法获取唯一的这个实例。

单例模式的优点

1.某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
2.系统中某些类,如spring里的controller,控制着处理流程,如果该类可以创建多个的话,系统完全乱了。
3.避免了对资源的重复占用。

单例模式的缺点

1.单例模式没有抽象层,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2.单例类的职责过重,在一定程度上违背了“单一职责原则”。
3.滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;又比如:在多个线程中操作单例类的成员时,但单例中并没有对该成员进行线程互斥处理。

单例模式的写法

1.饿汉式(两种)

package Singleton1;

/**
 * @ClassName Mgr01
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:21
 * @Version 1.0
 *
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全
 * 简单实用,推荐使用!
 * 唯一缺点:不管用到与否,类加载时就完成实例化(但是你不用的话写它干嘛)
 *
 **/
public class Mgr01 {

    private  static final Mgr01 INSTANCE = new Mgr01();
    private Mgr01(){};
    public void m() {
        System.out.println("m");
    }
    public static Mgr01 getInstance()
    {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Mgr01.getInstance().m();

    }
}
package Singleton1;

/**
 * @ClassName Mgr02
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:26
 * @Version 1.0
 **/
public class Mgr02 {
    private static final Mgr02 INSTANCE;
    static {
        INSTANCE = new Mgr02();
    }
    public static Mgr02 getInstance()
    {
        return INSTANCE;
    }
}

和第一种方式几乎没区别。

2.懒汉式(6种)

package Singleton1;

/**
 * @ClassName Mgr03
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:27
 * @Version 1.0
 **/
public class Mgr03 {
    private static Mgr03 INSTANCE;
    private Mgr03(){};
    public Mgr03 getInstance()
    {
        if(INSTANCE==null)
        {
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }
}

说明:此种方式线程不安全,当两个线程同时进入if判断INSTANCE是否为null时,因为INSTANCE还没被创建,所以会创建出多个不同的对象。

package Singleton1;

/**
 * @ClassName mGR04
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:31
 * @Version 1.0
 **/
public class Mgr04 {
    private static Mgr04 INSTANCE;
    private Mgr04(){};
    public static synchronized Mgr04 getInstance()
    {
        if(INSTANCE==null)
        {
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

}


说明:在此处由于我们采用static synchronized 锁住了当前Mgr04类(后面更新线程八锁问题),每个线程来都会锁住这个类,就会导致程序执行效率低下。

package Singleton1;

/**
 * @ClassName Mgr05
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:33
 * @Version 1.0
 **/
public class Mgr05 {
    private static Mgr05 INSTANCE;
    private Mgr05(){};
    public Mgr05 getInstance()
    {
        if(INSTANCE == null)
        {
         synchronized (Mgr05.class)
         {
             INSTANCE = new Mgr05();
         }
        }
        return INSTANCE;
    }
}

说明:此处会产生懒汉式第一种写法的线程安全问题

package Singleton1;

/**
 * @ClassName Mgr06
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:35
 * @Version 1.0
 **/
public class Mgr06 {
    private volatile static Mgr06 INSTANCE;
    private Mgr06(){};

    public Mgr06 getInstance()
    {
        if(INSTANCE == null)
        {
          synchronized (Mgr06.class)
          {
            if(INSTANCE == null)
            {
                INSTANCE = new Mgr06();
                return INSTANCE;
            }

          }
        }
        return INSTANCE;
    }



}

双检锁的机制有效的解决了上面的问题,这也是以前最推荐的方式。

注意:对象前面记得用volatile关键字修饰。因为创建对象不是一个原子性操作,但是操作系统为了提高效率会进行指令重排序,会导致程序在判断时出现误判现象。加了volatile关键字后,其中一个作用就是禁止指令重排序。
概述:
A、B线程同时进入了第一个if判断

A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

由于JVM内部的优化机制,JVM先划出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

package Singleton1;

/**
 * @ClassName Mgr07
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:39
 * @Version 1.0
 **/
public class Mgr07 {

    private Mgr07(){};
    private static class Holder{
       private final static Mgr07 INSTANCE = new Mgr07();
    }

    public static Mgr07 getInstance()
    {
        return Holder.INSTANCE;
    }

}

说明:
这个方法是一个很完美的方法,当jvm加载.class对象时,只会加载Mgr07.class,而不会加载内部类Holder。
当我们使用Holder时才会被jvm加载。这样就完美的解决了饿汉式的小毛病。做到了需要时才创建(懒加载)。

package Singleton1;

/**
 * @ClassName Mgr08
 * @Description
 * @Author SD.LIU
 * @Date 2021/7/7 22:42
 * @Version 1.0
 **/
public enum Mgr08 {
    INSTANCE;

}

使用枚举来实现单实例控制会更加简洁,而且JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。因为枚举类型无法反序列化,在其他方式我们都可以通过.class对象进行反射(newIntance()),然后创建对象。但是枚举类型没有构造函数所以无法被创建对象。

总结

推荐采用饿汉式或者静态内部类的方式创建单例对象,如果有特殊需求可以采用双检锁的方式去创建单例对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值