设计模式一: 单例模式

目录

饿汉式(静态常量) 优点:线程安全 缺点:可能浪费内存

懒汉式

双重检查(推荐使用)

静态内部类实现(推荐使用)

枚举实现(推荐使用)

JDK中的单例模式

单例模式注意事项和细节说明

介绍

所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
​
比如Hebernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessiobFacyory就够,这时就会使用到单例模式

单例模式有八种方式

1.饿汉式(静态常量)
2.饿汉式(静态代码块)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程安全,同步代码块)
6.双重检查
7.静态内部类
8.枚举

饿汉式(静态常量) 优点:线程安全 缺点:可能浪费内存

优缺点说明:

1. 优点: 这种写法比较简单,就是在类装载的时候就完成实例化.避免了线程同步问题.
​
2. 缺点: 在类装载的时候就实例化,没有达到lazy loading的效果.如果从始致终从未使用过这个实例,会造成内存的浪费
​
3. 这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类装载的时候就实例化,在单例模式大多数都是调用getInstance方法,
​
   但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候instance变量就没有达到懒加载的效果

补充:类加载的时机

对于什么时候加载,Java虚拟机规范中并没有约束,各个虚拟机都可以按自身需要来自由实现。但绝大多数情况下,都遵循“什么时候初始化”来进行加载。
​
什么时候初始化?Java虚拟机规范有明确规定,当符合以下条件时(包括但不限于),虚拟机内存中没有找到对应类型信息,则必须对类进行“初始化”操作:
​
使用new实例化对象时、读取或者设置一个类的静态字段或方法时
反射调用时,例如 Class.forName("com.xxx.MyTest")
初始化一个类的子类,会首先初始化子类的父类
Java虚拟机启动时标明的启动类
JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会其之前进行初始化

饿汉式(静态常量)

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);  //true
        System.out.println("instance.hashCode="+instance.hashCode());  //两个变量的hashCode相同
        System.out.println("instance1.hashCode="+instance1.hashCode());
    }
}

class Singleton {
    //1.将构造器私有化,防止外部能new
    private Singleton() {
    }

    //2. 在类内部创建对象实例
    public static final Singleton instance = new Singleton();
    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return  instance;
    }
}

饿汉式(静态代码块)

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);  //true
        System.out.println("instance.hashCode="+instance.hashCode());  //两个变量的hashCode相同
        System.out.println("instance1.hashCode="+instance1.hashCode());
    }
}

class Singleton {
    //1.将构造器私有化,防止外部能new
    private Singleton() {
    }

    //2. 在类内部创建对象实例
    private static  Singleton instance;
    static{  //静态代码块中实例化对象
     instance = new Singleton()
    }
    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return  instance;
    }
}

懒汉式

懒汉式(线程不安全)

/**
 * 懒汉式(线程不安全)
 */
public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton03 instance = Singleton03.getInstance();
        Singleton03 instance1 = Singleton03.getInstance();
        System.out.println(instance == instance1);
        System.out.println("instance.hashCode="+instance.hashCode());
        System.out.println("instance1.hashCode="+instance1.hashCode());
    }
}

class Singleton03 {
    private static Singleton03 instance;

    private Singleton03() {
    }

    public static Singleton03 getInstance() {
        if (instance == null) {
            instance = new Singleton03();
        }
        return instance;
    }
}

优缺点说明

<span style="background-color:#f8f8f8"><span style="color:#333333">1. 起到了懒加载的效果,但是只能在单线程下使用
2. 如果在多线程下,一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例.所以在多线程环境   下不可使用这种方式</span></span>

结论: 在实际开发中,不要使用这种方式

懒汉式(线程安全,同步方法,效率不高)

/**
 * 懒汉式(线程安全,同步方法)
 */
public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton03 instance = Singleton03.getInstance();
        Singleton03 instance1 = Singleton03.getInstance();
        System.out.println(instance == instance1);
        System.out.println("instance.hashCode="+instance.hashCode());
        System.out.println("instance1.hashCode="+instance1.hashCode());
    }
}

class Singleton03 {
    private static Singleton03 instance;

    private Singleton03() {
    }
	//加了synchronized,解决线程不安全问题
    public static synchronized Singleton03 getInstance() {
        if (instance == null) {
            instance = new Singleton03();
        }
        return instance;
    }
}

优缺点说明

1. 解决了线程不安全问题
2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步.而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行   了.方法进行同步效率太低(锁的粒度太大)
3. 结论: 在实际开发中,不推荐使用

双重检查(推荐使用)

/**
 *
 * 双重检查
 *
 **/
public class SingletonTest04 {
    public static void main(String[] args) {

    }
}

class Singleton04{
    private static volatile Singleton04 singleton04;  //volatile关键字保证一个线程实例化了该变量之后,其他线程能马上发现,避免再次实例化

    private Singleton04(){}


    public static Singleton04 getInstance(){
        if (singleton04==null){  //此判断拦截已经实例化后的线程,这些线程不需要再进入synchronized代码里,提高效率
            synchronized (Singleton04.class){
                if (singleton04==null){   //此判断拦截singleton04初始化时与初始化线程同时进入这里的线程,保证只有一个线程实例化singleton04
                    singleton04 = new Singleton04();
                }
            }
        }
        return singleton04;
    }
}

优缺点说明

1. 双重检查概念是多线程开发中常使用的,如代码中所示,我们进行了两次if (singleton04==null)检查,第一个检查提升效率,避免所有线程都进入同步代码,第二个检查结合volatile保证线程安全
2. 这样,实例化代码只执行一次,后面再次访问时,判断if (singleton04==null)直接return实例化对象,也避免的反复进行方法同步
3. 线程安全,延迟加载,效率较高
4. 结论: 在实际开发中,推荐使用这种单例模式

静态内部类实现(推荐使用)

public class SingletonTest05 {
    public static void main(String[] args) {
        Singleton05.test();
        Singleton05 instance = Singleton05.getInstance();
        Singleton05 instance2 = Singleton05.getInstance();
        System.out.println(instance == instance2);
    }
}

/**
 * 线程安全: 利用JVM的类装载实现线程安全
 * 懒加载: 静态内部类只负责类的初始化,不负责其他事情,保证了只有被第一次Singleton05.getInstance()才会初始化
 * */
class Singleton05{
    private Singleton05(){};

    public static void test(){
        //这个方法被调用不会导致静态内部类的加载
        System.out.println("外部类的静态方法被调用");
    }

    /**
     * 使用静态内部类的巧妙之处:
     * 1. 这个静态内部类只负责单例对象的初始化,不负责其他事情,其他事情会导致内部类的提前加载
     */
    private static  class SingletonInstance{
        private static final Singleton05 instance = new Singleton05();
        static {
            System.out.println("内部类被加载...");
        }
    }

    public static Singleton05 getInstance(){
        //这个方法第一次被调用时,加载SingletonInstance内部类,并将static成员变量instance初始化
        System.out.println("getInstance方法被调用");
        return SingletonInstance.instance;
    }
}
运行结果:
外部类的静态方法被调用
getInstance方法被调用
内部类被加载...
getInstance方法被调用
true

枚举实现(推荐使用)

/**
*优缺点说明:
* 1. 借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
*
**/
public enum Singleton {
    INSTANCE("张三","12");
    private String name;
    private String age;

    Singleton() {
    }

    Singleton(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public void ok(){
        System.out.println("ok");
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance==instance2);
        instance.ok();
    }
}

JDK中的单例模式

java.lang.Runtime使用了饿汉式创建单例模式

 

单例模式注意事项和细节说明

<span style="background-color:#f8f8f8"><span style="color:#333333">1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2. 当想实例化一个单例类的时候,必须要记住使用相应获取对象的方法,而不是使用new
3. 单例模式使用的场景: 需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)</span></span>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值