设计模式—单例模式
本文是关于设计模式中单例模式的 Java 代码实现详解
懒汉式
public final class Singleton {
private static Singleton instance;
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
测试代码
class test{
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance("FOO");
Singleton another = Singleton.getInstance("BAR");
System.out.println(singleton.value);
System.out.println(another.value);
}
}
输出
但是这种方法在多线程的使用场景下是不安全的,有可能会产生多个单例实例,比如把测试代码改成下面的形式
class test {
static class ThreadFoo implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("FOO");
System.out.println(singleton.value);
System.out.println("In Foo, " + singleton);
}
}
static class ThreadBar implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("Bar");
System.out.println(singleton.value);
System.out.println("In Bar, " + singleton);
}
}
public static void main(String[] args) {
Thread threadFoo = new Thread(new ThreadFoo());
Thread threadBar = new Thread(new ThreadBar());
threadFoo.start();
threadBar.start();
}
}
运行结果就会发生变化
饿汉式
多线程场景下安全使用单例模式的一种方法是饿汉式
public class Singleton {
// 在类加载的时候就创建并初始化
private static Singleton instance = new Singleton("harry");
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
测试代码不变,运行结果如下
可以看到,两个实例的 hashcode
是相同的,是同一个对象。但是使用这种方法需要在类加载的时候就初始化实例,占用内存资源,不推荐使用这种方法。
双重检测
双重检测是目前常用的线程安全单例模式的方式,它有两点改变:
- 在实例上加关键字
volatile
,防止指令重排序,因为指令重排序可能会导致Singleton
对象被 new 出来,并且赋值给instance
之后,还没来得及初始化,就被另一个线程使用了 - 在
getInstance
方法中对创建实例的代码加锁,保证只有一个线程能创建示例
public class Singleton {
// 加入 volatile 关键字防止指令重排
private static volatile Singleton instance;
public String value;
private Singleton(String value){
this.value = value;
}
public static synchronized Singleton getInstance(String value){
if(instance == null){
// 加类级别的锁
synchronized(Singleton.class){
// 避免多线程并发时多次创建对象
if(instance == null){
instance = new Singleton(value);
}
}
}
return instance;
}
}
测试代码不变,运行结果如下
静态内部类
用静态内部类的方式实现单例类,利用了Java 静态内部类的特性
Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例
SingletonInner
是一个静态内部类,当外部类 Singleton
被加载的时候,并不会创建 SingletonInner
实例对象。
只有当调用 getInstance
方法时,SingletonInner
才会被加载,这个时候才会创建 instance
。instance
的唯一性、创建过程的线程安全性,都由 JVM 来保证。
public class Singleton {
public String value;
private Singleton(String value) {
this.value = value;
}
private static class SingletonInner {
private static final Singleton instance = new Singleton("harry");
}
public static synchronized Singleton getInstance(String value) {
return SingletonInner.instance;
}
}
测试代码不变,运行结果如下
枚举
用枚举来实现单例,是最简单的方式。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
public enum Singleton {
INSTANCE("Singleton instance");
Singleton(String s) {}
}
测试代码如下
class test {
static class ThreadFoo implements Runnable {
@Override
public void run() {
System.out.println("In Foo, " + Singleton.INSTANCE);
}
}
static class ThreadBar implements Runnable {
@Override
public void run() {
System.out.println("In Bar, " + Singleton.INSTANCE);
}
}
public static void main(String[] args) {
Thread threadFoo = new Thread(new ThreadFoo());
Thread threadBar = new Thread(new ThreadBar());
threadFoo.start();
threadBar.start();
}
}
运行结果