设计模式一单例模式学习笔记

本文深入解析单例模式的概念、意义及六种经典实现方法,包括饿汉式、懒汉式、同步延迟加载、双重校验锁、静态内部类及枚举,探讨其在多线程环境下的线程安全问题。

1、概念

java一种常用它设计模式,在这个设计模式中,单列对象的类保证存在一个实例,该类自己创建自己对象,并保证对象唯一,并对外提供一个返回该对象的方法。可以直接访问不需要再实例化。

2 意义

为什么需要单列模式?

如果一个全局类需要被频繁使用,过度创建和销毁对象将大大降低系统性能,单例模式在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例、避免对资源的多重占用(比如写文件操作)。

*列如一个班只有一个班长;

*多进程多线程的系统,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。

总之:单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。

三要素:

构造方法私有化;

一个指向自己私有的静态对象引用;

一个返回自己实例的静态公共方法;

 

3 立即加载和延迟加载

  • 立即加载 : 在类加载初始化的时候就主动创建实例;如饿汉式

  • 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。 代表懒汉式

 

4单线程下的单例模式

1饿汉式

public class Single {
    private Single(){
    }
    private static Single s=new Single();
    public static Single get(){
        return s;
    }
}

在这种方法下,由于在加载该类时就会对该类进行实例化,并且类只会加载一次,所以在调用该返回该对象方法,实例已经被创建,所以该方法可以保证实例绝对单一,但是在一定程度下造成了内存资源的浪费。

 

2 懒汉式

  private Single(){
    }
    private static Single s;

    public static Single get(){
        if (s==null){
            s=new Single();
        }
        return s;
    }

在类被加载时并不会创建自己的实例,只有调用创建实例方法是在会创建,使用这种延迟加载在一定程度上减少内存资源浪费。

 

总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些

 

5多线程下的单列模式

作为两种经典的单例创建模式,饿汉式,懒汉式在单线程中都可以保证对象的单一,但是在多线程中已经存在出一些线程安全问题。

在多线程中,饿汉式依然能够保证实例单一,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。

但是在懒汉式中,如果多个线程同时进入到 if (s==null){s=new Single();}代码块中,就有可能创建出多个实例。

 

1同步延迟加载---使用synchronized

public class Single {
    private Single(){
    }
    private static Single s;

    public static synchronized Single get(){
        if (s==null){
            s=new Single();
        }
        return s;
    }
}

如果返回该对象的方法使用synchronized修饰,可以保证单例,使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

那什么是同步?

* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

但是在加锁在使用上会影响性能。

 

2.双重校验锁

public class Single {
    private Single(){
    }
    private static volatile Single s;
    public static  Single get(){
        if (s==null) {
            synchronized (Single.class) {
                if (s == null) {
                    s = new Single();
                }
            }
        }
        return s;
    }
}

在这种方法中,不但能够保证单一实例,而且提高性能,但是实现难度较为复杂。

使用双重判断,第一次调用该方法是,如果为空,进入同步代码块,如果非空,直接跳过,有效的避开了过多的同步,第一次创建实例时才使用到了同步。

但是在使用该方法时必须使用 volatile 修饰实例应用。

volatile作用:

首先需要知道jvm在使用new 实例化一个对象的基本步骤:

     1:分配对象的内存空间

      2:初始化对象

     3:使对象引用指向刚分配的内存地址

 

但是这只是一种情况,在实际情况中,指令可能是无序的,(实现指令重排序)有可能执行顺序为1 3 2,由于在使用new 实例化一个对象是一个非原子性的操作,所以可能发生以下情况:

线程1执行到了第9行

使对象引用指向刚分配的内存地址,但是未初始化对象, 被线程 2 预占; 线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 Singleton 对象; 

如果使用volatile 修饰,能够防止指令的重排序。避免得到一个不完整(未初始化)的 Singleton 对象

 

3静态内部类

public class Single {
    private Single(){
    }

    private static class Sin{
        private static  Single s=new Single();
    }

    public static  Single get(){
     return Sin.s;
    }
}

该方法达到了双重校验锁一样的效率,实现更为简单,但是这种方法只适应于静态域的情况。

对比饿汉式,该方法同样保证了单例,但是在加载该类时,不一定实例化对象,只有通过显式调用 get方法时,才会显式装载 Sin类,从而实例化.

 

4 枚举

这是我新了解到的一种方法,于 JDK1.5 之后才加入 enum 特性,这种实现方式还没有被广泛采用,这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它更简洁,自动支持序列化机制,能避免多线程同步问题,绝对防止多次实例化。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

 

注意点!!

在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题

 

小结:

  以上就是我对单例模式概述一点心得,以及了解到的6种经典实现单例方法。当然现实中还有其他方法,但是这集中比较经典。文章如有错误,欢迎批评指点。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值