JVM内存结构 VS Java内存模型 VS Java对象模型:https://blog.csdn.net/qq_40794973/article/details/103960056
JMM应用实例:单例模式8种写法、单例和并发的关系(真实面试超高频考点)
单例模式的作用
- 为什么需要单例:节省内存和计算、保证结果正确、方便管理
private Resource rs = new Resource();
public Resource getExpensiveResource(){
return rs;
}
public Resource(){
field1 = //some CPU heavy logic
field2 = //some value from DB
field3 = //etc.
}
单例模式适用场景
- 无状态的工具类:比如日志工具类,不管是在哪里使用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象即可。
- 全局信息类:比如我们在一个类上记录网站的访问次数,我们不希望有的访问被记录在对象A上,有的却记录在对象B上,这时候我们就让这个类成为单例。
/**
* 饿汉式(静态常量)(可用)
*/
public class Singleton {
private final static Singleton instance = new Singleton();
// 构造函数私有化
private Singleton() {
//....
}
public static Singleton getInstance() {
return instance;
}
}
/**
* 饿汉式(静态代码块)(可用)
*/
public class Singleton {
private final static Singleton instance;
static {
instance = new Singleton();
}
// 私有构造函数
private Singleton() {
//...
}
public static Singleton getInstance() {
return instance;
}
}
注意!!!
/**
* 写法不当容易产生NPE
*/
public class Singleton {
static {
instance = new Singleton();
}
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
}
}
/**
* 懒汉式(线程不安全)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 懒汉式(线程安全)(不推荐)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
//多个线程无法并行执行,不能及时的响应
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 懒汉式(线程不安全)(不推荐)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
//并没有解决线程安全问题
if (instance == null) {
//这里虽然是同步的,但是还是会多次运行
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
优点:线程安全;延迟加载;效率较高。
为什么要double-check
- 线程安全
- 单check行不行?//可 第四种
- 性能问题
为什么要用volatile
- 新建对象实际上有3个步骤
- 重排序会带来NPE
- 防止重排序
/**
* 双重检查(推荐面试使用)
*/
public class Singleton {
//注意volatile
//1.可见性
//2.防止重排序
private volatile static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
//虽然可能进来多个,但是我再做一次检查即可避免多次创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
不加volatile产生错误分析
//非线程安全
public class Singleton {
// 单例对象
private static Singleton instance = null;
// 私有构造函数
private Singleton() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
// 静态的工厂方法
public static Singleton getInstance() {
if (instance == null) { // 线程2:线程1已经执行了重排序后的前两个指令,instance!=mull 了但是单例对象还没有初始化完成,这个时候返回的是未初始化完成的对象
synchronized (Singleton.class) { // 同步锁
if (instance == null) {
instance = new Singleton(); // 线程1:在执行指令重排后的3步
}
}
}
return instance;
}
}
/**
* 静态内部类方式,可用
*/
public class Singleton {
private Singleton() {
//...
}
//JVM类加载的性质,保证了即便多个线程同时访问这个SingletonInstance,也不会创建多个实例
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
懒汉,调用这个方法才会初始化 ;加载一个类时,其内部类不会同时被加载
public class Singleton {
static {
System.out.println("Singleton被初始化啦...");
}
private Singleton() {
//...
}
private static class SingletonInstance {
static {
System.out.println("Singleton里面的静态内部类被初始化啦...");
}
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
public static void main(String[] args) {
//Singleton.getInstance();//只有调用这个方法,Singleton里面的静态内部类才会被初始化
}
}
/**
* 枚举单例(推荐生产实践中使用)
*/
public enum Singleton {
//INSTANCE
INSTANCE;
/**
* 类中的方法
*/
public void whatever() {
System.out.println("whatever");
}
}
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.whatever();
}
}
另一种写法
public class SingletonExample {
// 私有构造函数
private SingletonExample() {
}
public static SingletonExample getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample();
}
public SingletonExample getInstance() {
return singleton;
}
}
}
- 饿汉:简单,但是没有 lazy loading
- 懒汉:有线程安全问题
- 静态内部类:可用
- 双重检查:面试用
- 枚举:最好
- Joshua Bloch大神在《Effective java》中明确表达过的观点:“使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。”
- 写法简单
- 线程安全有保障
- 避免反序列化破坏单例
- 最好的方法是利用枚举,因为还可以防止反序列化重新创建新的对象
- 非线程同步的方法不能使用
- 如果程序一开始要加载的资源太多,那么就应该使用懒加载
- 饿汉式如果是对象的创建需要配置文件就不适用
- 懒加载虽然好,但是静态內部类这种方式会引入编程复杂性
饿汉式的缺点?
- 优点:写法简单,线程安全
- 缺点:程序启动就加载,不需要这个实例也加载了,造成资源的浪费
懒汉式的缺点?
- 优点:解决了前期加载浪费的问题,只在需要的时候才加载进来
- 缺点:写法相对比较复杂,不注意容易写成线程不安全的情况
为什么要用 double-check?不用就不安全吗?
- 为什么单重锁是线程池不安全的
- 逐渐演变升级到双重锁代码 //懒加载 线程安全
- synchronized加方法上虽然安全,但是效率底响应慢
为什么双重检査模式要用volatile?
应该如何选择,用哪种单例的实现方案最好?
//饿汉式
//java.lang.Runtime
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
//构造方法私有化
private Runtime() {}