单例模式-设计模式

单例模式是一种设计的模式,因为很多东西在整个应用的过程中只需要一个全局的对象。不需要多个,例如重复点击回收站,返回的是同一个对象,没有必要创建多个对象。

        优点:

  • 在内存中只有一个对象,节省内存空间
  • 避免频繁的创建销毁对象,可以提高性能
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。
  • 缺点:1.不适用于变化频繁的对象  ?因为频繁变化的对象在一般过程中可以通过上一层进行功能的拓展。但是对于单例模式而言,我们必须去修改对象本身。

        2.单例对象的责任过重,不太符合单一职责原则。(单一职责原则指的是,我们在实际实际应用中,长将一个对象,只赋予一个职能,避免在修改的同时,影响其他职能的使用)

        3.滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;(√)

多线程操作单例对象会导致线程异常。

又比如:在多个线程中操作单例类的成员时,但单例中并没有对该成员进行线程互斥处理。

                

适用的场景:

  • 1.需要生成唯一序列的环境。
  • 2.需要频繁实例化然后销毁的对象。
  • 3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 
  • 4.方便资源相互通信的环境。

        例如:无状态工具类,全局信息类。

实现单例模式(7)

饿汉式和懒汉式。

首先 饿汉式的理解,就是在类加载时,先创建了对象。(主动创建)

方法1:

public class Singleton1 {
    private final static Singleton1 Instance = new Singleton1();

    private Singleton1() {
    }
    public static Singleton1 getInstance(){
        return Instance;
    }
}

方法2:(静态代码块)

public class Singleton2 {
    private static Singleton2 Instance;
    static {
        Singleton2 Instance = new Singleton2();
    }

    private Singleton2() {
    }
    public static Singleton2 getInstance(){
        return Instance;
    }
}

优点:类装载时,直接完成了实例化。避免了线程的同步问题。

缺点:如果一直没有使用,那么浪费内存。未LazyLoading

懒汉式:在需要使用这个对象时,才实例化对象。(被动创建)

注意:在懒汉式中,静态的代码只是先声明了对象,此时值为null,在计算机中未实例化的对象不占用内存。故可以节约空间。

 

 

实现单例模式

饿汉式和懒汉式。

首先 饿汉式的理解,就是在类加载时,先创建了对象。(主动创建)

public class HungrySingleton{

    private static HungrySingleton create = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getHungrySingleton (){

        return create ;

    }

}

优点:类装载时,完成了实例化。避免了线程同步问题。

缺点:如果一直没有使用,那么浪费内存。未LazyLoading

懒汉式:在需要使用这个对象时,才实例化对象。(被动创建)

注意:在懒汉式中,静态的代码只是先声明了对象,此时值为null,在计算机中未实例化的对象不占用内存。故可以节约空间。

方式3:懒汉式-有线程安全问题。

/**
 * @program:多线程和IO
 * @descripton:懒汉式-线程不安全如果出现了线程竞争的情况,那么会出现两个实例。
 * @author:ZhengCheng
 * @create:2021/9/29-16:50
 **/
public class Singleton3 {
    private static Singleton3 Instance = null;
    private Singleton3() {
    }
    public static Singleton3 getInstance(){
        if (Instance == null){
            Instance = new Singleton3();
        }
        return Instance;
    }
}

  我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

方式4:我们使用一个synchronized关键字保护getInstance,但是这样保护之后,会导致运行的速率大大下降。

/**
 * @program:多线程和IO
 * @descripton:懒汉式-线程安全-不推荐
 * @author:ZhengCheng
 * @create:2021/9/29-16:54
 **/
public class Singleton4 {
    private static Singleton4 Instance = null;
    private Singleton4(){
    }
    public static synchronized Singleton4 getInstance(){
        if (Instance == null){
            Instance = new Singleton4();
        }
        return Instance;
    }
}

      方式5:那么,用synchronized包着方法效率太低,那我们包着创建对象的代码,是不是就提高了效率。

/**
 * @program:多线程和IO
 * @descripton:懒汉式-线程不安全
 * @author:ZhengCheng
 * @create:2021/9/29-16:56
 **/
public class Singleton5 {
    private static  Singleton5 Instance = null;
    private Singleton5(){

    }
    public static  Singleton5 getInstance(){
        if (Instance == null){        阻塞1
            synchronized (Singleton5.class){
                    Instance = new Singleton5();
            }
        }
        return Instance;
    }
}

上述的代码看似很安全,但是,实际上是不安全的!!!

因为假设有两个线程,他们同时通过了判断,此时Instance确实是null,那么现在他们都来到阻塞1,开始抢锁。第一个线程拿到了锁,创建对象,第二个线程之后也会拿到锁,再次创建对象,这就有了两个对象,线程是不安全的。所以我们需要使用Doublecheck的方式,来保证线程安全问题。

方式6:Double-Check 推荐使用

public class Singletin6 {
    private volatile static Singletin6 Instance = null;
    private Singletin6(){

    }
    public static Singletin6 getInstance(){
        if (Instance == null){
            synchronized (Singletin6.class){
                if (Instance == null){
                    Instance = new Singletin6();
                }
            }
        }
        return Instance;
    }
}

        从方式6中,我们看到,创建了该单例模式,我们使用了两次判断,以解决方式5中出现的问题。解决了上述问题,那为什么我在声明Instance还使用了Volatile,这是为什么呢?

        原因在于避免重排序的影响。首先,我们知道,给除了long和double等基本类型赋值是原子性的,给引用类型赋值也是原子性的,但是,我们现在在赋值时,需要new一个对象。这个操作,并不是原子性的。这个赋值存在三个操作:1.创建一个空白对象 2.调用构造方法实例化对象 3.赋值。如果发生了重排序,使得2.3交换了运行顺序,假设Thread1刚好赋值成功,切到Thread2,Thread2拿到锁一判断,发现对象不是空的,那么就会跳过创建语句,直接return。那么return了以后,Thread2发现被坑了,拿到的实例是空的,于是会出现空指针异常。

        所以为了保证不发生重排序,我们使用volatile关键字来对Instance进行保护。

方式7:使用静态内部类的方法(线程安全,总体还不错,但是会提高代码的复杂度)

/**
 * @program:多线程和IO
 * @descripton:使用静态内部类的方法 目前是懒汉!并且Safe 推荐
 * @author:ZhengCheng
 * @create:2021/9/30-9:28
 **/
public class Singleton7 {
    private Singleton7(){

    }
    private static class SingletonInstance{
        private static final Singleton7 Instance = new Singleton7();
    }
    public static Singleton7 getInstance(){
        return SingletonInstance.Instance;
    }
}

 

方式8:最佳实现单例模式的方法,枚举类

/**
 * @program:多线程和IO
 * @descripton:枚举单例模式最佳写法
 * 使用枚举类类似静态类,直接调用。
 * @author:ZhengCheng
 * @create:2021/9/30-9:42
 **/
public enum Singleton8 {
    Instance;//创建枚举实例
    public void doWhatUWant(){
        
    }
}

 

        在《Effective Java》中:“使用枚举实现单例的方法虽然还没有广泛采用,但是段素的枚举类型已经成为实现Singleton的最佳方法”

        ♦ 写法简单        ♦线程安全        ♦ 避免反序列化破坏单例

各种写法的适用场合

        ♦ 最好的方法是利用枚举

        ♦ 不安全的方法不使用

        ♦ 如果程序一开始要加载的资源太多,应当适用懒加载

        ♦ 饿汉式如果对象的创建需要配置文件就不适用。

        ♦ 懒加载虽然好,但是静态内部类的方式会引入编程复杂性

面试常见问题:

        ♦ 饿汉式的缺点?

        ♦ 懒汉式的缺点?

        ♦ 为什么使用Double-Check?不用就不安全吗?

        ♦ 为什么双重检查模式需要Volatile

        ♦ 应该如何选择,用哪种单例的实现方式最好?

总体复习问题:

        1.JMM应用实例:8种单例模式,单例和并发的关系

        2.什么是JMM

        3.volatile和synchronized的异同?

        4.什么是原子操作?Java有哪些原子操作?生成对象的过程是不是原子操作?

        5.什么是内存可见性?

        6.64位long和double写入的时候是原子的吗?

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值