Java设计模式有很多种,这些设计模式是为了约束代码,保证代码的正确性,本次介绍单例模式,是Java开发中比较典型的开发模式。
1.单例模式:单例模式意味着单个实例(对象),即在实现某个类的同一个进程中,只能出现一个实例(对象);实例(Instance)也是一个很广泛的词:一个对象,一个进程,一个服务器...
2.实现一个单例模式的类:
2.1:
static:修饰方法和属性,被此关键词修饰之后是类属性 / 类方法 ,不带static修饰的方法和属性称为 " 实例方法 / 实列属性";
这里在借助static,把类属性创建出来了,由于instance的属性是static,这里的初始化(new)操作,只会在该类(Singleton这个类)被加载时初始化一次;
java
class Singleton{
private static Singleton instance =new Singleton();
}
2.2:饿汉模式的单例模式:
通过这三个步骤就可以创建出一个单例模式的类
java
class Singleton{
//通过static的作用,把这个类实例创建出来
private static Singleton instance =new Singleton();
//如果想使用这个实例,需要用方法获取他
public static Singleton getInstance(){
return instance;
}
//还需要防止程序员通过其他途径创建Singleton的实例
//这里的核心方法是把类的构造方法设置为private
private Singleton(){}
}
2.3:
这里再main方法内部再创建一个Singleton实例,发现会报出如下的报错提示,说明此时这个类遵循单例模式。

上面这种写法,创建单例的时机是在类加载的时候创建的,可以近似认为Java一创建就加载这个类 ,但是这样会有一个问题,在程序启动时,如果有很多这样的单例模式的类,那么就会扎堆初始化 ,这样可能会是服务器爆满,所以Java给出一种 " 懒汉模式 " 的单例模式,可以很好的解决在这个问题。
3.懒汉模式的单例模式(非必要,不创建new):
所谓懒汉模式就是可以有程序员决定创建单例类的时机,这里的创建时机由第一次调用getInstance()方法来决定;
java
class Singletonlazy{
//此处设置为null不会被初始化,只有new的时候才会被初始化;
private static Singletonlazy Instance =null;
public static Singletonlazy getInstance(){
if(Instance==null){
//对Instance进行初始化
Instance = new Singletonlazy();
}
return Instance;
}
private Singletonlazy(){};
}
那么他们是否线程安全呢?

很明显是不安全的,这里我们先介绍一下Java的GC(垃圾回收)机制;
3.1GC机制:GC(Garbage Collection,垃圾回收) 是自动内存管理机制:识别并释放不再被引用的对象,避免内存泄漏与溢出,减少手动管理出错。
3.2:

再2步骤中t2将线程调度走后会创建(new)一次新对象,而当t1再次调度走线程后,会根据上下文再次创建(new)一次对象,这导致对象被创建了两次,随着第二次的创建完成,第一个对象就没有引用指向了,会被GC给释放掉,虽然最后结果确实是只剩下了一个对象,但我们仍然认为这里是存在bug的;
3.3:
这里根据之前学过的知识,可以对这个操作加锁,保证其原子性,就可以避免3.2的问题;
java
private static Singletonlazy Instance =null;
public static Singletonlazy getInstance() {
synchronized (Singletonlazy.class) {
if (Instance == null) {
//对Instance进行初始化
Instance = new Singletonlazy();
}
}
return Instance;
}
这样加上锁之后,就可以保证在Instance实例在初始化之后,代码不涉及到修改,从而达到天然 " 线程安全 "。但是这样的代码只要线程一调度就自动加锁,导致开销很大性能变低,锁竞争很激烈,我们可以对代码做出下面的修改:在加锁之前先判定Instance是否被初始化,如果已经初始化过了,就不再进行加锁操作,这样大大降低了锁竞争的激烈程度。
DCL双重检查锁定:这也就是DCL双重检查锁定,第一层if判断是否需要加锁;第二层if判断是否需要进行初始化。
java
class Singletonlazy{
//此处设置为null不会被初始化,只有new的时候才会被初始化;
private static Singletonlazy Instance =null;
public static Singletonlazy getInstance() {
if (Instance == null) {
synchronized (Singletonlazy.class) {
if (Instance == null) {
//对Instance进行初始化
Instance = new Singletonlazy();
}
}
}
return Instance;
}
private Singletonlazy(){};
}
举个例子来解释:

4.指令重排序引起的线程安全问题(只有这里存在这个问题!!!)
指令重排序也是编译器优化的一种手段,在保证逻辑不变的前提下,按照更优化的顺序来执行指令;

解决方案也很简单:加上volatile关键字,禁止编译器在读或写的时候进行优化。
java
class Singletonlazy{
//此处设置为null不会被初始化,只有new的时候才会被初始化;
private volatile static Singletonlazy Instance =null;
public static Singletonlazy getInstance() {
if (Instance == null) {
synchronized (Singletonlazy.class) {
if (Instance == null) {
//对Instance进行初始化
Instance = new Singletonlazy();
}
}
}
return Instance;
}
private Singletonlazy(){};
}
这样,就构成了一个完整的单例模式!