Java单例模式学习
参考
https://www.cnblogs.com/alsf/p/5517504.html
https://mp.weixin.qq.com/s/nSzXTeSDLNL9CR0Gnt94kw
单例模式概念
单例模式,顾名思义就是在内存只有会创建一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象.
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
注意事项:
- getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化
懒汉模式和双重校验加锁
懒汉模式创建对象的方法是在程序使用对象前, 先判断该对象是否实例化, 如果已经实例化, 直接返回类对象, 否则执行实例化操作.

代码如下
public class instanceTest {
private static instanceTest instance;
private instanceTest() {
};
public static instanceTest getInstance() {
if (instance == null) {
instance = new instanceTest();
}
return instance;
}
}
懒汉模式的问题
如果两个线程同时判断 instance 为空,那么它们都会去实例化一个instance 对象,这就变成多例了。所以要解决的是线程安全问题。

最容易想到的解决方法就是在方法上加锁
public static synchronized instanceTest getInstance() {
if (instance == null) {
instance = new instanceTest();
}
return instance;
}
或者对类对象加锁
public static instanceTest getInstance() {
synchronized (instanceTest.class) {
if (instance == null) {
instance = new instanceTest();
}
}
return instance;
}
这样规避了两个线程同时创建Instance对象的风险,但是每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象
性能优化:
如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例, 所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁
修改代码如下
public static instanceTest getInstance() {
if (instance == null) {
synchronized (instanceTest.class) {
if (instance == null) {
instance = new instanceTest();
}
}
}
return instance;
}
上面的代码已经完美地解决了并发安全 + 性能低效问题
(1) 如果 Instance不为空,则直接返回对象,不需要获取锁;而如果多个线程发现 Instance 为空,则进入分支.
(2) Instance为空之后, 多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断Instance 是否为空,因为Instance 有可能已经被之前的线程实例化
(3) 其它之后获取到锁的线程发现 Instance 已经不为空了,则不会再 new 一个对象,直接返回对象即可
(4) 之后所有进入该方法的线程都不会去获取锁,在第一次判断 Instance 对象时已经不为空了
(5) 因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)
完整代码如下
public class instanceTest {
// 声明成员变量, static能在外部访问到
// 声明private, 访问需要使用getInstance访问器
private static instanceTest instance;
// 构造函数是私有的, 对除了instance之外的参数进行初始化构造
private instanceTest() {};
// 声明static可以直接外部访问, 外部通过静态方法访问进行实例化
// 使用getInstance对instance进行封装, getInstance是构造入口, 同时返回
// 静态方法内的变量也是静态
public static instanceTest getInstance() {
if (instance == null) { // 检查实例是否已经存在, 否则创建实例instance实例对象
synchronized (instanceTest.class) { // 多线程排队创建
if (instance == null) {
instance = new instanceTest(); // 由于是私有化构造器, 外部无法实例化, 所以直接在内部实例化掉
}
}
}
return instance;
}
public static int myFunc(int number) {
return number + 1;
}
}
(1) 在外部调用构造器可以创建不同的类实例, 单例设计模式核心就是将类的构造方法私有化, 之后在类的内部产生实例化对象
(2) 在外部直接调用类的静态方法getInstance, 在内部实例化, 并且返回实例化对象给外部
(3) 内部instance通过private私有化不能直接访问, 需要调用getInstance访问器, instance需要static和getInstance静态方法保持一致
(4) 内部构造器对除了instance外的变量进行初始化构造
测试
public class useInstance {
public static void main(String[] args) {
int a = instanceTest.getInstance().myFunc(2);
System.out.println(a);
}
}
单例模式编写一个Solr查询方法
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
public class SolrClientTest {
private static SolrClientTest instance;
private HttpSolrServer SolrServer;
private SolrClientTest() {
SolrServer = new HttpSolrServer("http://localhost:8080/solr/collection3");
}
public static SolrClientTest getInstance() {
if (instance == null) {
synchronized (SolrClientTest.class) {
if (instance == null) {
instance = new SolrClientTest();
}
}
}
return instance;
}
public static void getEvent(String query) {
SolrQuery params = new SolrQuery(query);
try {
//执行查询,获取响应数据
QueryResponse response = SolrClientTest.getInstance().SolrServer.query(params); // 在这里实例化
//获取数据结果集
SolrDocumentList list = response.getResults();
System.out.println("一共获取了" + list.size() + "条结果:");
for (SolrDocument solrDocument : list) {
System.out.println("ent:" + solrDocument.getFieldValue("ent"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试调用
public class useInstance {
public static void main(String[] args) {
SolrClientTest.getEvent("id:\"1201\""); // 直接通过静态方法调用, 在getEvent中完成实例化
}
}
或者
public void getEvent(String query) {
SolrQuery params = new SolrQuery(query);
try {
//执行查询,获取响应数据
QueryResponse response = this.SolrServer.query(params);
//获取数据结果集
SolrDocumentList list = response.getResults();
System.out.println("一共获取了" + list.size() + "条结果:");
for (SolrDocument solrDocument : list) {
System.out.println("ent:" + solrDocument.getFieldValue("ent"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
测试调用
public class useInstance {
public static void main(String[] args) {
SolrClientTest.getInstance().getEvent("id:\"1201\""); // 在这里实例化
}
}
单例模式传参
在getInstance中加入参数,如果参数有更新不会调用新的参数,因为已经存在单例
https://blog.csdn.net/liu_yanzhao/article/details/72626605
public class EntAliasMongoUtils {
private static EntAliasMongoUtils instance;
private static MongoClient mongoClient;
private EntAliasMongoUtils(String MongoHost, String MongoPort) {
mongoClient = new MongoClient(MongoHost, parseInt(MongoPort));
}
public static EntAliasMongoUtils getInstance(String MongoHost, String MongoPort) {
if (instance == null) {
synchronized (EntAliasMongoUtils.class) {
if (instance == null) {
instance = new EntAliasMongoUtils(MongoHost, MongoPort);
}
}
}
return instance;
}
}