【单例模式】懒汉模式的线程安全问题

文章目录

  • 单例模式
    • 饿汉式
    • 懒汉式
      • 懒汉式1,线程不安全
      • 懒汉式2,线程安全
      • 懒汉式3,双检锁/双重校验锁
  • volatile关键字


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

单例模式

什么是设计模式?

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

什么是单例模式?

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

注意
 1、单例类只能有一个实例。
 2、单例类必须自己创建自己的唯一实例。
 3、单例类必须给所有其他对象提供这一实例。

回到目录…

饿汉式

是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

public class StarvingMode {
    // 是线程安全的
    // 类加载的时候执行
    // JVM 保证了类加载的过程是线程安全的
    private static StarvingMode instance = new StarvingMode(); // 一开始就实例化对象

    // 其它对象获取该实例的方法
    public static StarvingMode getInstance() {
        return instance;
    }

    // 构造方法私有化,确保只有这一个实例对象
    private StarvingMode() {}
}

回到目录…

懒汉式

懒汉式1,线程不安全

是否多线程安全:否
描述:没有加锁,线程不安全,严格意义不算单例模式。

public class LazyModeV1 {
    // 先不急着实例化对象
    private static LazyModeV1 instance = null;
    private LazyModeV1() {}
    public static LazyModeV1 getInstance() {
        // 第一次调用这个方法时,说明我们应该实例化对象了
        if (instance == null) {
        	// 只在第一次的时候执行
            instance = new LazyModeV1();
        }
        return instance;
    }
}

懒汉式2,线程安全

是否多线程安全:是
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

public class LazyModeV2 {  
    private static LazyModeV2 instance;  
    private LazyModeV2(){}  
    public static synchronized LazyModeV2 getInstance() {  
        if (instance == null) {  
            instance = new LazyModeV2();  
        }  
        return instance;  
    }  
}

懒汉式3,双检锁/双重校验锁

是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class LazyModeV3 {  
	// 防止代码重排序出现问题
    private volatile static LazyModeV3 instance;  
    private LazyModeV3(){}  
    public static LazyModeV3 getInstance() {  
    	// 第一次校验
    	if (instance == null) {  
    		// 确定需要创建时,再抢夺锁
        	synchronized (LazyModeV3.class) {  
        		// 抢到锁后再次检验,保证只会被实例化一次
            	if (instance == null) {  
                	instance = new LazyModeV3();  
                	// 当重排序成 1 -> 3 -> 2 的时候可能出问题
                    // 通过 volatile 修复
            	}  
        	}  
    	}  
   		return instance;  
    }  
}

回到目录…

volatile关键字

volatile有何作用?

 ①volatile修饰变量 :要求线程每次从主内存读入变量,并保证写回主存。保证了内存可见性

 ②volatile SomeObject so; // 修饰对象解决了对象实例化过程中重排序的问题

对象实例化的过程
 1.分配内存给对象,在内存中开辟一段地址空间;// Singleton var = new Singleton();
 2.对象的初始化;// var = init();
 3.将分配好对象的地址指向instance变量,即instance变量的写;// instance = var;

 设想一下,如果不使用volatile关键字限制指令的重排序,1-3-2操作,获得到单例的线程可能拿到了一个空对象,后续操作会有影响!因此需要引入volatile对变量进行修饰。

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java多线程的学习,学习了单例模式下的饿汉式和懒汉式,并且解决了懒汉式的线程安全问题,最后也了解的关键字volatile的用法。之后的学习内容将持续更新!!!

你可能感兴趣的:(Java多线程与并发,java,单例模式,jvm)