本文重要关注点:html
单例模式属于管理实例的创造型类型模式。单例模式保证在你的应用种最多只有一个指定类的实例。java
读取项目的配置信息的类能够作成单例的,由于只须要读取一次,且配置信息字段通常比较多节省资源。经过这个单例的类,能够对应用程序中的类进行全局访问。无需屡次对配置文件进行屡次读取。设计模式
日志器Logger在你的应用中是无处不在的。也应该只初始化一次,可是能够处处使用。安全
若是你在使用一些数据分析工具例如Google Analytics。你就能够注意到它们被设计成单例的,仅仅初始化一次,而后在用户的每个行为中均可以使用。多线程
将默认的构造器设置为private。阻止其余类从应用中直接初始化该类。并发
建立一个public static 的静态方法。该方法用于返回一个单例类实例。oracle
还能够选择懒加载初始化更友好。ide
示例代码参见如下类工具
public class Singleton {
private static Singleton instance;
// 构造器私有化
private Singleton(){
}
// 提供静态方法
public static Singleton getInstance(){
// 懒加载初始化,在第一次使用时才建立实例
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void display(){
System.out.println("Hurray! I am create as a Singleton!");
}
}
复制代码
单元测试类:性能
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(){
final Set<Singleton> sets = new HashSet<>();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
Singleton s = Singleton.getInstance();
sets.add(s);
}
});
}
System.out.println(sets);
}
}
复制代码
运行输出以下,结果生成了多个Singleton实例:
[org.byron4j.cookbook.designpattern.singleton.Singleton@46b91344, org.byron4j.cookbook.designpattern.singleton.Singleton@1f397b96]
线程安全对于单例类来讲是很是重要的。上述Singleton类是非线程安全的,由于在线程并发的场景下,可能会建立多个Singleton实例。
为了规避这个问题,咱们能够将 getInstance 方法用同步字 synchronized 修饰,这样迫使线程等待直到前面一个线程执行完毕,如此就避免了同时存在多个线程访问该方法的场景。
public static synchronized Singleton getInstance() {
// Lazy initialization, creating object on first use
if (instance == null) {
instance = new Singleton();
}
return instance;
}
复制代码
这样确实解决了线程安全的问题。可是,synchronized
关键字存在严重的性能问题。咱们还能够进一步优化 getInstance 方法,将实例同步,将方法范围缩小:
public static Singleton getInstance() {
// Lazy initialization, creating object on first use
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
复制代码
单元测试三种方式耗时比较:
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(){
final Set<Singleton> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
Singleton s = Singleton.getInstance();
sets.add(s);
}
});
}
System.out.println("test用时:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testSynchronized(){
final Set<SingletonSynchronized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
SingletonSynchronized s = SingletonSynchronized.getInstance();
sets.add(s);
}
});
}
System.out.println("testSynchronized用时:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testOptimised(){
final Set<SingletonSynchronizedOptimized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
SingletonSynchronizedOptimized s = SingletonSynchronizedOptimized.getInstance();
sets.add(s);
}
});
}
System.out.println("testOptimised用时:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
}
复制代码
运行测试用例,输出以下:
test用时:1564
[org.byron4j.cookbook.designpattern.singleton.Singleton@68eae58e]
testSynchronized用时:3658
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized@36429a46]
testOptimised用时:2254
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized@21571826]
复制代码
能够看到,最开始的实现方式性能是最好的,可是是非线程安全的; Synchronized 锁住整个getInstance方法,能够作到线程安全,可是性能是最差的; 缩小Synchronized范围,能够提升性能。
涉及单例类时还要注意clone方法的正确使用:
package org.byron4j.cookbook.designpattern.singleton;
/** * 单例模式实例 * 1. 构造器私有化 * 2. 提供静态方法供外部获取单例实例 * 3. 延迟初始化实例 */
public class SingletonZClone implements Cloneable{
private static SingletonZClone instance;
// 构造器私有化
private SingletonZClone(){
}
// 提供静态方法
public static SingletonZClone getInstance(){
// 将同步锁范围缩小,下降性能损耗
if(instance == null){
synchronized (SingletonZClone.class){
if(instance == null){
instance = new SingletonZClone();
}
}
}
return instance;
}
/** * 克隆方法--改成public * @return * @throws CloneNotSupportedException */
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void display(){
System.out.println("Hurray! I am create as a SingletonZClone!");
}
}
复制代码
默认状况下clone时protected修饰的,这里改成了public修饰,测试用例以下:
@Test
public void testClone() throws CloneNotSupportedException {
SingletonZClone singletonZClone1 = SingletonZClone.getInstance();
SingletonZClone singletonZClone2 = SingletonZClone.getInstance();
SingletonZClone singletonZClone3 = (SingletonZClone)SingletonZClone.getInstance().clone();
System.out.println(singletonZClone1 == singletonZClone2);
System.out.println(singletonZClone1 == singletonZClone3);
System.out.println(singletonZClone2 == singletonZClone3);
}
复制代码
输出以下:
true
false
false
咱们了解一下clone方法的API解释, clone 后的对象虽然属性值多是同样的,可是已经不是同一个对象实例了:
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
clone方法返回一个被克隆对象的实例的副本,除了内存地址其余属性值都是同样的,因此副本和被克隆对象不是同一个实例。 能够看出clone方法破坏了单例类,为防止该问题出现,咱们须要禁用clone方法,直接改成:
/** * 克隆方法--改成public * @return * @throws CloneNotSupportedException */
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
复制代码
Java序列化机制容许将一个对象的状态转换为字节流,就能够很容易地存储和转移。 一旦对象被序列化,你就能够对其进行反序列化--将字节流转为对象。 若是一个Singleton类被序列化,则可能建立重复的对象。 咱们可使用钩子hook,来解释这个问题。
在Java规范中有关于readResolve()方法的介绍:
对于可序列化的和外部化的类,readResolve() 方法容许一个类能够替换/解析从流中读取到的对象。 经过实现 readResolve 方法,一个类就能够直接控制反序列化后的实例以及类型。 定义以下:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; 复制代码
readResolve 方法会在ObjectInputStream 从流中读取一个对象时调用。ObjectInputStream 会检测类是否认义了 readResolve 方法。 若是 readResolve 方法定义了,会调用该方法用于指定从流中反序列化后做为返回的结果对象。 返回的类型要与原对象的类型一致,否则会出现 ClassCastException。
@Test
public void testSeria() throws Exception {
SingletonZCloneSerializable singletonZClone1 = SingletonZCloneSerializable.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(singletonZClone1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializable test = (SingletonZCloneSerializable) ois.readObject();
ois.close();
System.out.println(singletonZClone1 == test);
}
复制代码
测试输出: false; 说明反序列化的时候已经不是原来的实例了,如此会破坏单例模式。
因此咱们能够覆盖 readResolve 方法来解决序列化破坏单例的问题:
类 SingletonZCloneSerializableReadResolve 增长 readResolve 方法:
/** * 反序列化时返回instance实例,防止破坏单例模式 * @return */
protected Object readResolve(){
return getInstance();
}
复制代码
执行测试用例:
@Test
public void testSReadResolve() throws Exception {
s = SingletonZCloneSerializableReadResolve.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(s);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializableReadResolve test = (SingletonZCloneSerializableReadResolve) ois.readObject();
ois.close();
System.out.println(s == test);
}
复制代码
输出true,有效防止了反序列化对单例的破坏。
单例类是不多使用的,若是你要使用这个设计模式,你必须清楚的知道你在作什么。由于全局范围内仅仅建立一个实例,因此在资源受约束的平台是存在风险的。
注意对象克隆。 单例模式须要仔细检查并阻止clone方法。
多线程访问下,须要注意线程安全问题。
当心多重类加载器,也许会破坏你的单例类。
若是单例类是可序列化的,须要实现严格类型