您的位置:首页 > 编程语言 > Java开发

Java单例模式知识点详解

2017-03-15 22:04 405 查看

Java 单例模式概念

单例模式:确保在java程序中,一个类class只有一个实例,并自行实例化,向整个程序提供这个实例。

好处:

1. 这样可以节省内存,限制了实例的个数,利于回收;

2. 保证了资源类的同步操作,避免了并发问题。

Java 单例模式的写法

常见的单例模式写法有:懒汉式,饿汉式,双重校验锁,静态内部类,枚举。

懒汉式,线程不安全

/**
* 单例模式——懒汉式[线程不安全]
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {
private static Singleton instance;

private Singleton() {
}

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


此种写法,当多线程的时候, if (instance == null) {…}会出现并发问题,进而产生多个实例(就不是单例了)。

改进版——懒汉式,线程安全

/**
* 单例模式——懒汉式[线程安全]
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {
private static Singleton instance;

private Singleton() {
}

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


这里对getInstance方法加了synchronized同步锁。避免了多线程时因并发而产生多个实例的问题。

但是此种做法,无论是否已实例化都做同步锁使线程挂起,效率很低。可以仅针对需要实例化的情况进行同步加锁。

再次改进版——双重校验锁

/**
* 单例模式——双重校验锁
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {
private static Singleton instance;

private Singleton() {
}

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


这里仅仅对 if (instance == null) {}的情况以加锁块的形式做同步。并且针对多线程时,并发进入 if (instance == null) {}内的情况做优化,增加了一步非空判断。这也就是双重校验锁单例模式。

这里,既对多线程情况做了优化,又保证了效率。这样就真的没问题了吗?不见得吧~

这里引入java中的指令重排优化概念~

指令重排优化指的是在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。Java的JVM可以自由的进行指令重排优化处理。

在JVM创建新的对象时,主要要经过三步:

1. 分配内存

2. 初始化构造器

3. 将对象指向分配的内存的地址

由于有指令重排优化机制的存在,所以第二步和第三步的顺序是可以改变的。也就是会出现分配了指向分配内存的地址却还没有初始化构造器的情况。这时候会得到一个初始化失败的实例,若紧接着调用getInstance方法,返回的将是一个不正确的对象实例,程序就会报错。

这就是双重校验锁会失效的原因。不过还好在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。如下:

/**
* 单例模式——双重校验锁(volatile)
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {
private static volatile Singleton instance;

private Singleton() {
}

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


这样的再次优化,就彻底解决双重校验锁带来的问题了。

饿汉式,线程安全

/**
* 单例模式——饿汉式(线程安全)
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {
private static final Singleton instance = new Singleton();

private Singleton() {
}

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


如此看来,是不是饿汉式的单例写法非常的简单哦~

单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

唯一的问题是该单例会在加载类时就对对象进行初始化,即未调用getInstance时已经初始化了,没有实现用时才初始化的懒加载模式。如果Singleton类不是很消耗资源,也无可厚非啦~

静态内部类

/**
* 单例模式——静态内部类
* Created by jinzifu on 2017/3/26.
*/

public class Singleton {

private static class SingletonChild {
private static final Singleton instance = new Singleton();
}

private Singleton() {
}

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


此种单例模式写法,起到了懒加载的效果,并且在第一次调用getInstance时加载类Singleton到内存时就已初始化,确保了线程安全。

枚举

/**
* 单例模式——枚举
* Created by jinzifu on 2017/3/26.
*/

public enum Singleton {
INSTANCE;

public void getName() {
System.out.println("使用enum实现单例模式");
}
}


注意:

1. 可以在枚举属性后面添加()来调用指定参数的构造方法,添加{}来实现其对应的匿名内部类。

2. 枚举式单例不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

这里任意写了一个方法getName(),外部可以如此调用:

Singleton singleton = Singleton.INSTANCE;

singleton.getName();

个人分述:单例模式的几种写法已经整理完毕,这里比较推荐静态内部类单例模式和枚举单例模式,饿汉式单例模式在目标类不是很消耗资源时也可以使用。这几种方式都是线程安全的。其他的方式不做推荐。

其他的设计模式我们接下来继续学习~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息