所谓“懒汉式”与“饿汉式”的区别,是在与建立单例对象的时间的不同。
- “懒汉式”是在你真正用到的时候才去建这个单例对象
- “饿汉式是在类创建的同时就已经创建好一个静态的对象,不管你用的用不上,一开始就建立这个单例对象
代码实现:
- 懒汉模式:
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出错场景:
以第一段代码为例,我们可以模拟一下两条线程运行的情况:
- 线程A 访问了getInstance 方法,发现两次 singleton== null 检查都是 true,于是线程A 开始初始化 Singleton2 此处时间点设为 x
- 在时间点 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 关键字也可以尝试把这个域改造成不可变的对象—— 原理是读和写一个不可变对象都是原子性的,也就不可能读到一个初始化未完成的不可变对象。
饿汉式
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;
}
}