Java单例模式

Posted 盛夏群岛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java单例模式相关的知识,希望对你有一定的参考价值。

 

懒汉模式与饿汉模式

懒汉模式就是懒加载,用到的时候去加载,存在线程安全问题,需要手动地加锁控制。它的优点是类加载的速度比较快,按需加载,节省资源。

饿汉模式就是在类加载的时候会创建出实例。它天生就不存在线程安全问题。但是类加载的速度会变慢且耗费资源。

 

懒汉模式-单重检查

示例代码如下:

public class LazySingleton {

    private static LazySingleton singletoninstance = null;
    private Object data = new Object();

//私有化构造方法
private LazySingleton(){ } //加锁访问 public static synchronized LazySingleton getInstance(){ if(singletoninstance == null){ singletoninstance = new LazySingleton(); } return singletoninstance; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }

测试代码如下:

public class TestThread extends Thread {

    @Override
    public void run() {

        LazySingleton instance = LazySingleton.getInstance();
        System.out.println(instance.getData());
    }
}

public static void main(String[] args) {

        for(int i =0;i < 10;i++){
            TestThread t = new TestThread();
            t.start();
        }
    }
}

运行结果如下:


java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64

打印出同一个object对象,表明是从同一个LazySingleton对象中获取的数据。

但是上述代码存在一个显著的问题:多个线程同时访问getInstance()方法都是排队式的,即使该instance已经被创建的情况下。然而,如果该instance已经被创建,是可以支持并发访问的。需要对锁的控制细粒度化。

 

懒汉模式-双重检查

public class LazySingleton {
//声明为volatile变量
    private static volatile LazySingleton singletoninstance = null;
    private Object data = new Object();

    private LazySingleton(){

    }

    public static synchronized LazySingleton getInstance(){

        if(singletoninstance == null){
            synchronized (LazySingleton.class) {
                //这个第二重的的检查是必要的
                if(singletoninstance == null)
                   singletoninstance = new LazySingleton();
            }
        }
        return singletoninstance;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

第二重检查是为了防止:

线程A发现instance未被创建,于是申请锁,进入临界区创建instance;于此同时另一个线程也发现instance未被创建,于是也要申请锁去创建instance,问题就这样发生了。而且,这个instance变量要被声明为volatile,也就是其中一个线程对它就行修改之后(也就是实例化),这一修改立马对其他线程可见,避免了无谓的等待。

检查代码同上,运行结果同上。

 

饿汉模式

public class HungerSingleton {

    private static final HungerSingleton singletoninstance = new HungerSingleton();
    private Object data = new Object();

    private HungerSingleton(){

    }

    public static HungerSingleton getInstance(){

        return singletoninstance;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

}

在加载该类的时候就立马去实例化instance,不存在线程安全问题(由jvm保证线程安全问题),但是存在资源浪费、加载速度慢的问题。

检查代码同上,运行结果同上。

 

Holder模式

就是利用一个静态内部类来实现instance的实例化。这里利用了静态内部类的一个特性:该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载

public class HolderSingleton {

    private Object data = new Object();

    private HolderSingleton(){

    }
    
    private static class InnerClass{

        private static HolderSingleton singletoninstance = new HolderSingleton();
    }

    public static HolderSingleton getInstance(){

        return InnerClass.singletoninstance;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

测试代码同上,运行结果同上。

在加载InnerClass的时候才会去实例化这个instance,实现了延迟加载,并且同饿汉模式一样,由jvm保证线程安全。这种方法值得推荐

 

应用场景:

在整个系统中,只允许共用一个实例的类适合用单例模式来实现,比如:

网站的计数器,只允许存在一个计数器实例;

线程池,只允许存在一个线程池对象;

连接池,只允许存在一个连接池对象

 

以上是关于Java单例模式的主要内容,如果未能解决你的问题,请参考以下文章

java 单例模式这个要怎么理解?

Java中的单例模式

Java设计模式--单例模式(代码详解懒汉饿汉模式)

Java设计模式之单例模式

Java 设计模式——单例模式 理论代码相结合

常用代码片段