所谓“懒汉式”与“饿汉式”的区别,是在与建立单例对象的时间的不同。

  •  “懒汉式”是在你真正用到的时候才去建这个单例对象
  • “饿汉式是在类创建的同时就已经创建好一个静态的对象,不管你用的用不上,一开始就建立这个单例对象

代码实现:

  • 懒汉模式:
public class Singleton2 {

       private volatile static Singleton2 singleton; // 5

       private Singleton2() {
             System.out.println("懒汉式单例初始化!");
       }
       public static Singleton2 getInstance () {
             if(singleton ==null) { // 1
                   synchronized(Singleton2.class) { // 2
                   if(singleton == null) { // 3
                    singleton = new Singleton2(); //4
                   }}
             }
        return singleton;
       }

}

 代码1 处的判空是为了减少同步方法的使用,提高效率
代码2,3 处的加锁和判空是为了防止多线程下重复实例化单例。
代码5 处的volatile是为了防止多线程下代码4 的指令重排序 

Volatile关键字的作用: 禁止进行指令的重排序

模拟没有volatile出错场景:

以第一段代码为例,我们可以模拟一下两条线程运行的情况:

  1. 线程A 访问了getInstance 方法,发现两次 singleton== null 检查都是 true,于是线程A 开始初始化 Singleton2 此处时间点设为 x
  2. 在时间点 x 线程B 访问了 getInstance 方法,此时它发现 singleton已经是非 null 了,于是高高兴兴地返回了 Singleton2 实例,并且外部调用开始使用 Singleton2 的实例,此处时间点设为 y

问题在于:时间点 y 的 singletonInstance 实例真的初始化好了吗

由于第一段代码里忽略了 SingletonInstance 类的其他实例域,我们可以假设充实一下SingletonInstance 类

public class SingletonPattern {

    private int num;
    private boolean really;

    private SingletonPattern(){
      this.num = 2020;
      this.really = true;
    }


    private static SingletonPattern singletonInstance;

    public static SingletonPattern getSingletonInstance() {
        if (singletonInstance == null) {
            synchronized (SingletonPattern.class) {
                if (singletonInstance == null) {
                    singletonInstance = new SingletonPattern();
                }
            }
        }
        return singletonInstance;
    }
}

在JVM新建一个对象实例时,首先会赋予这个实例的所有域初始值(比如 int 的初始值是 0, boolean的初始值是 false),然后再调用构造器方法。也就是说,有那么一个时刻(比如就是时间点 y )新建的SingletonPattern实例的 num 值为 0, really 值为 false。

此外,JVM 的指令重排可能导致了这个对象实例先被赋值给了变量,然后才开始初始化,所以时间点 y 变量已经不为 null,而实例尚未初始化结束。

正在 时间点 y,线程B已经开始使用了SingletonPattern实例,这就是一个 bug 了,因为SingletonPattern实例根本还没初始化结束!

类似的,第二段懒汉式代码一样有这个问题。

既然上面那两段代码有问题,那如何修正呢?

很简单,加个关键字就好了 —— volatile

静态域版本

public class SingletonPattern {

    // ... 忽略其他域定义和方法

    private static volatile SingletonPattern singletonInstance;

    public static SingletonPattern getSingletonInstance() {
        if (singletonInstance == null) {
            synchronized (SingletonPattern.class) {
                if (singletonInstance == null) {
                    singletonInstance = new SingletonPattern();
                }
            }
        }
        return singletonInstance;
    }
}

实例域版本

public class MyClass {
    private volatile MyField field;

    public MyField initField() {
        if (field == null) {
            synchronized (MyClass.class) {
                if (field == null) {
                    field = new MyField();
                }
            }
        }
        return field;
    }
}

为什么 volatile 可以解决以上问题?

依然以静态域版本为例,当 volatile 修饰了singletonInstance变量之后,就会禁止JVM对这个变量相关操作做指令重排 —— 即在调用 new SingletonPattern() 初始化SingletonPattern实例时(写操作),JVM 会保证SingletonPattern实例完全初始化结束后才允许其他线程访问(读操作)这个实例。

有更好的实现吗?

对于静态域的延迟初始化, lazy initialization holder class 模式不仅性能更好,代码可读性也更高

public class SingletonPattern {

    // ... 忽略其他域定义和方法

    private static class SingletonPatternHolder{
       static final SingletonPattern instance = new SingletonPattern();
    }

    public static SingletonPattern getSingletonInstance() {
        return SingletonPatternHolder.instance;
    }
}

不算括号,4行代码就搞定

当getSingletonInstance()被调用时,SingletonPatternHolder类才第一次被JVM加载,且JVM会以线程安全的方式初始化好SingletonPatternHolder类的静态域——也就是 SingletonPatternHolder.instance

所以 getSingletonInstance() 是线程安全的。

 

而实例域的延迟初始化的这种双重检查模式,除了加 volatile 关键字也可以尝试把这个域改造成不可变的对象—— 原理是读和写一个不可变对象都是原子性的,也就不可能读到一个初始化未完成的不可变对象。

饿汉式

 

懒汉模式 java_懒汉模式 java

public class Singleton1 {

    private final static Singleton1 singleton = new Singleton();

    private Singleton1() {
        System.out.println("饿汉式单例初始化!");
    }
    public static Singleton1 getSingleton () {
        return singleton;
    }
}

 

在类静态变量里直接new一个单例

 

 

懒汉模式构建ESRestAPIUtils

import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class ESRestAPIUtils {
    /**
     * 声明一个对象的引用
     */
    private static volatile RestClient restClient;
    private static volatile RequestOptions COMMON_OPTIONS;
    /**
     * 私有构造方法
     */
    private ESRestAPIUtils(){
    }
    /**
     * @DESC: 初始化ES客户端
     */
    private static RestClient initClient() {
        RestClientBuilder builder = RestClient.builder(assembleESHost().toArray(new HttpHost[assembleESHost().size()]));
        Header[] header = new Header[]{new BasicHeader("header", "value")};
        builder.setDefaultHeaders(header);
        return builder.build();
    }
    /**
     * @DESC: 初始化RequestOptions
     */
    private static RequestOptions initRequestOptions(){
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        builder.setHttpAsyncResponseConsumerFactory(
                new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(800 * 1024 * 1024));
        return builder.build();
    }

    /**判断一下如果对象为null,在创建一个对象
     * 多线程的中使用的时候,线程优化,加一个锁
     * @DESC: 获取RequestOptions 的COMMON_OPTIONS对象,懒汉模式
     */
    public static RequestOptions getRequestOptions() {
        if (null == COMMON_OPTIONS) {
            synchronized (ESRestAPIUtils.class) {
                if (null == COMMON_OPTIONS) {
                    try {
                        COMMON_OPTIONS = initRequestOptions();
                        //esClusterClient.settings();
                    } catch (Exception e) {
                        log.error("RequestOptions创建失败...." + COMMON_OPTIONS, e);
                    }
                }
            }
        }
        return COMMON_OPTIONS;
    }
    /**判断一下如果对象为null,在创建一个对象
     * 多线程的中使用的时候,线程优化,加一个锁
     * @DESC: 获取RestClientBuilder,懒汉模式
     */
    public static RestClient getRestClient() {
        if (null == restClient) {
            synchronized (ESRestAPIUtils.class) {
                if (null == restClient) {
                    try {
                        restClient = initClient();
                        //esClusterClient.settings();
                    } catch (Exception e) {
                        log.error("ESClient创建失败...." + restClient, e);
                    }
                }
            }
        }
        return restClient;
    }
/**
     * @return : 返回请求后的json字符串
     * @DESC: 可接受所有对于ES的查询操作,包括数据写入、删除、一般的条件查询和聚合查询
     */
    public static String query(String queryJson, String method, String endpoint) {
        String resultJson = "";
//        RestClient restClient = restClient.build();
        //     .setMaxRetryTimeoutMillis(10 * 60 * 1000)/**设置查询超时时长,默认30秒*/.build();
        HttpEntity entity = new StringEntity(queryJson, ContentType.APPLICATION_JSON);//说明提供的是一个json格式的query
        Response response;
        try {
            Request request = new Request(method, endpoint);
            request.setEntity(entity);
            request.addParameter("pretty", "true");
            RequestOptions options = ESRestAPIUtils.getRequestOptions();
            request.setOptions(options);
            RestClient client = ESRestAPIUtils.getRestClient();
            response = client.performRequest(request);
            resultJson = EntityUtils.toString(response.getEntity(), "UTF-8");
//            try { //为什么要去掉关闭restclient:懒汉的目的,就是让服务始终保持有一个RestClient,如果我们关闭后,新启的查询又需要去重新new一个对象,这样浪费时间。懒汉的初衷就是空间换时间。
//                client.close();//关闭后,对象并不为空
//                client =null;//设置对象为空
//            } catch (IOException e) {
//                log.error("Rest客户端关闭失败...", e);
//            }
            return resultJson;
        } catch (IOException e) {
            log.error("查询请求失败..." + queryJson, e);
            return resultJson;
        }
    }

    /**
     * 通过ConsulUtils获取ES的节点配置,并封装为HttpHost
     * @DESC: 组装ES的hosts为HttpHost
     */
    private static List<HttpHost> assembleESHost() {
        ArrayList<HttpHost> esHostList = new ArrayList<>();
        String[] esAddrArray = ConfigUtils.getSingleConf("es/cluster.nodes").split(",");

        for (String es : esAddrArray)
            esHostList.add(new HttpHost(es, Integer.valueOf(ConfigUtils.getSingleConf("es/cluster.http.port"))));
        return esHostList;
    }
}