`

java单例模式的正确写法

阅读更多

一、懒汉式(线程不安全)

 

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

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

 

介绍:线程不安全,在多线程情况下容易创建多个实例。

 

 

二、懒汉式(线程安全)

 

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

  

介绍:虽然线程安全,但是不够高效。

 

 

三、双重检验锁

 

public static Singleton getSingleton() {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

 

介绍双重检验锁机制比懒汉式(线程安全)要高效很多,因为它不用对instance不为空的情况进行同步。这段代码看起来似乎很完美,但很可惜,还是存在问题。instance=new Singleton() 这并非是一个原子性质的操作,事实上在JVM中做了如下三件事:

 

  1. 给instance分配内存
  2. 调用构造函数初始化成员变量
  3. 将instance对象指向分配的内存(此步骤完成instance即为非空)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

 

四、双重检验锁(volatile)

 

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}

    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
   
}

 

介绍:使用 volatile 的原因是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。这里使用volatile的一个主要原因就是volatile可以禁止JVM指令重排序。

 

五、饿汉式

 

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

 

 介绍:这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。缺点是它不是一种懒加载模式。比如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

 

五、静态内部类

 

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

 

 介绍:这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

 

六、枚举

 

public enum EasySingleton {
	
	INSTANCE;
	
	EasySingleton(){
		
	}
	
}

 

介绍:我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

 

 

2
2
分享到:
评论
5 楼 lgh1992314 2018-08-07  
使用 volatile 的原因是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。

这句有问题:
volatile 保证的是每次更新都及时的写入到主存。
4 楼 Nick_Wing 2016-07-25  
wosyingjun 写道
luowuhua16 写道
双重检验锁,在被锁的代码部分,为什么还要再次检验是否为null?如果代码执行到被锁的部分,说明还是null

因为可能有多个线程通过第一个null判断,如果不加第二个null判断的话会创建多个实例。

第一步的 Single Checked 我觉得没有必要呀,因为   synchronized (Singleton.class)这个已经锁住了其他线程对这块锁包围的代码块的访问,那肯定是等到当前线程实例化完成之后释放才能允许其他线程进行访问的,这边没有明白双重check的原因?求解
3 楼 wosyingjun 2016-04-25  
luowuhua16 写道
双重检验锁,在被锁的代码部分,为什么还要再次检验是否为null?如果代码执行到被锁的部分,说明还是null

因为可能有多个线程通过第一个null判断,如果不加第二个null判断的话会创建多个实例。
2 楼 luowuhua16 2016-04-22  
双重检验锁,在被锁的代码部分,为什么还要再次检验是否为null?如果代码执行到被锁的部分,说明还是null
1 楼 451914442 2016-04-21  
高手!66666

相关推荐

    java单例模式详解Java系列2021.pdf

    Java语言中的单例模式实现有多种方式,包括饿汉式、懒汉式和登记式单例。不同的实现方式适应不同的场景和需求,各有优劣。 首先,饿汉式单例是类加载时就会立即初始化,它的好处是实现简单,且在多线程环境下能保证...

    单例的多种写法和说明比较

    但在多线程环境下,如果没有正确同步,可能导致多个线程同时创建单例,违背了单例模式的初衷。 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static...

    Java:单例模式的七种写法

    枚举在Java中是一个特殊的语法结构,它天然支持单例模式,INSTANCE是Singleton枚举类型下的一个元素,相当于单例的实例,通过Singleton.INSTANCE可以直接访问到单例。此外,枚举的写法简洁明了,易于理解和维护。 ...

    单例模式七种写法_转

    综上所述,单例模式和双重检查锁定的正确实现需要程序员深入理解多线程编程、Java内存模型以及设计模式。在实践中,应当根据具体需求和环境选择合适的实现策略,并注意相关技术的适用性和潜在问题,以确保代码的健壮...

    Java编写《电话本系统》课程设计

    11. **设计模式**:为了提高代码的可维护性和可扩展性,可以应用设计模式,如工厂模式用于创建联系人对象,单例模式用于确保数据库连接的唯一性,观察者模式用于实时更新UI。 12. **测试**:良好的软件工程实践包括...

    JAVA经典笔试题目

    - **设计模式**:了解单例模式、工厂模式、观察者模式等常用的设计模式及其应用场景。 - **JVM内存模型**:了解JVM内存结构,包括堆内存、栈内存、方法区等组成部分,掌握垃圾回收机制。 - **数据库连接管理**:熟悉...

    春招&秋招面经

    - **内部类方式**:利用Java内部类的特性来实现单例模式,这种方式也是线程安全的,且支持延迟加载。 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { ...

    《Java基础入门》复习资料(打印).doc.docx

    17. **单例设计模式**:单例模式确保一个类只有一个实例,并提供一个全局访问点。它通常通过私有构造方法、静态变量和静态工厂方法实现。 在学习Java基础时,理解并掌握这些概念是至关重要的,它们构成了编写高效、...

    dvd管理系统

    6. **设计模式**:在开发过程中,良好的设计模式如单例模式(用于管理共享资源,如数据库连接)和工厂模式(用于创建DVD对象)可以使代码结构更加清晰,提高可复用性和可维护性。 7. **测试与调试**:使用JUnit等...

    Android SharedPreferences存储的正确写法

    1. **单例模式**:通过`Context.getSharedPreferences()`获取的SharedPreferences对象是单例的,意味着在整个应用中只会有一个实例。这样确保了数据的一致性,避免了多个副本可能导致的冲突。 2. **编辑器模式**:`...

    java 面试题2

    15. **设计模式:**常见的设计模式有单例模式、工厂模式、观察者模式等,用于解决软件设计中的常见问题。 16. **JavaScript数字验证:**可以使用正则表达式或其他函数如isNaN()进行数字类型的验证。 17. **CORBA:...

    java高级工程师面试题

    1. **单例模式**:确保一个类只有一个实例,并提供一个全局访问点。例如`Runtime.getRuntime()`。 2. **工厂模式**:提供创建对象的接口,让子类决定实例化哪个类。如`java.util.Calendar.getInstance()`。 3. *...

    神洲数码招程序员面试题

    9. **单例模式**:这段代码展示了典型的单例模式实现,确保一个类只有一个实例。`MyTest`类中的`getInstance`方法保证了这一点。`t1`和`t2`都是同一个实例,而`t3`是新的实例。因此,当修改`t2.i`和`t3.i`后,`t1.i`...

    MyDatabaseHelper

    设计上,它可能采用单例模式确保全局只有一个实例,以避免资源浪费和并发问题。同时,它也可能包含事务处理机制,保证数据的一致性。 二、核心功能 1. 数据库初始化:`MyDatabaseHelper`在初始化时会创建或打开...

    2021-2022计算机二级等级考试试题及答案No.10037.docx

    声明构造方法时,可以使用private关键字来限制其访问范围,通常用于实现单例模式等设计模式。 #### 数据完整性中的实体完整性 - **知识点**: 数据库完整性约束中的实体完整性。 - **详细解析**: 实体完整性是一种...

    详解Dagger2在Android开发中的新用法

    然而,对于初学者或者熟悉旧版Dagger2的开发者来说,可能会对某些设计模式感到困惑,尤其是如何在不同组件之间建立联系以及如何在不违反依赖注入原则的情况下实现功能。本文将详细解析Dagger2的新用法,特别是针对...

Global site tag (gtag.js) - Google Analytics