单例模式简记
Class diagram exemplifying the singleton pattern.In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. [ 维基百科 ]
最近看了一些设计模式的书,对一些常见的设计模式做一个总结记录。其中单例模式是经常使用并且简单的一种设计模式。
简单理解
个人理解 :
简单来说就是一个类只有一个对象实例,普通的类可以用构造函数创造多个对象实例。
普通的类就好像模板,可以印刷出多个对象,而单例限制了只能印刷一次。
实现方式
- 懒汉式创建单例(线程不安全)
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton lazySingleton;
public static LazySingleton getLazySingleton(){
//先判断是否为空
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
- 懒汉模式的改进版(线程安全,但使用了锁,会影响性能)
public class LazyThreadSafeSingleton {
private LazyThreadSafeSingleton(){}
public static LazyThreadSafeSingleton lazyThreadSafeSingleton;
//通过synchronized
public synchronized static LazyThreadSafeSingleton getInstance(){
if (lazyThreadSafeSingleton == null){ //1
lazyThreadSafeSingleton = new LazyThreadSafeSingleton(); //2
}
return lazyThreadSafeSingleton; //3
}
}
仔细看整个方法会发现,实际上只需要添加锁在初始化方法上即第2步中就可以了,方法中其它语句并没有同步问题,修改后的代码如下
//通过synchronized
public static LazyThreadSafeSingleton getInstance(){
if (lazyThreadSafeSingleton == null){ //1
synchronized (LazyDoubleLockSingleton.class){
lazyThreadSafeSingleton = new LazyThreadSafeSingleton(); //2
}
}
return lazyThreadSafeSingleton; //3
}
- 饿汉模式创建单例
//饿汉模式创建单例
private static SingleClass singleClass = new SingleClass();
//将构造方法私有
private SingleClass(){}
public static SingleClass getInstance(){
return singleClass;
- 静态内部类
这种方法创建单例模式比较简单,而且在创建过程中属于懒加载形式,利用JVM 本身机制解决了同步问题,在类初始化时内部类没有进行初始化,在第一次引用是进行初始化。
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){}
private static class Holder{
private static LazyInnerClassSingleton
lazyInnerClassSingleton = new LazyInnerClassSingleton();
}
public static LazyInnerClassSingleton getInstance(){
return Holder.lazyInnerClassSingleton;
}
}
- 双重锁, 与第2种方式有区别,不是简单的加锁。第2种方式是直接加锁,无论实例是否存在,在高并发情况下会影响性能,而双重锁会提高性能。
public class LazyDoubleLockSingleton {
private LazyDoubleLockSingleton(){}
private volatile static LazyDoubleLockSingleton lazyDoubleLockSingleton;
//通过synchronized 和 volatile变量
public static LazyDoubleLockSingleton getInstance(){
if (lazyDoubleLockSingleton == null){
synchronized (LazyDoubleLockSingleton.class){
if (lazyDoubleLockSingleton == null){
lazyDoubleLockSingleton = new LazyDoubleLockSingleton();
}
}
}
return lazyDoubleLockSingleton;
}
}
Q : 为什么一定要加volatile ?
A : 网上有资料说原因是不加volatile 时会导致重复实例化。
实验验证 :
第一步 : 新增单例模式类LazyDoubleLockSingleton1
public class LazyDoubleLockSingleton1 {
private LazyDoubleLockSingleton1(){}
private volatile static LazyDoubleLockSingleton1 lazyDoubleLockSingleton;
//通过synchronized 和 volatile变量
public static LazyDoubleLockSingleton1 getInstance(){
if (lazyDoubleLockSingleton == null){
synchronized (LazyDoubleLockSingleton1.class){
if (lazyDoubleLockSingleton == null){
System.out.println("lazyDoubleLockSingleton1 is initial");
lazyDoubleLockSingleton = new LazyDoubleLockSingleton1();
}else{
System.out.println("lazyDoubleLockSingleton1 is exists");
}
}
}
return lazyDoubleLockSingleton;
}
}
第二步 : 新增不含volaitle 修饰的单例模式
public class LazyDoubleLockSingleton2 {
private LazyDoubleLockSingleton2(){}
private static LazyDoubleLockSingleton2 lazyDoubleLockSingleton;
//通过synchronized 和 volatile变量
public static LazyDoubleLockSingleton2 getInstance(){
if (lazyDoubleLockSingleton == null){
synchronized (LazyDoubleLockSingleton2.class){
if (lazyDoubleLockSingleton == null){
System.out.println("lazyDoubleLockSingleton2 is initial");
lazyDoubleLockSingleton = new LazyDoubleLockSingleton2();
}else{
System.out.println("lazyDoubleLockSingleton2 is exists");
}
}
}
return lazyDoubleLockSingleton;
}
}
运行线程,奇怪的是,实验过程中,当线程数从100 增加到1000时并没有预料的变化,继续查阅资料跟进。
后来发现其中没有看起来那么简单,跟汇编、JVM 实现等底层有关,最主要的是跟重排序有关。简单来说就是不加volaitle
的话初始化在编译后的执行顺序有三步,而且顺序是随机的,有可能导致并发问题。而volaitle 在java 5 之后可以完美实现
单例双重锁(关键本机实验中也没出现重复初始化问题,这部分还需要实验证明)。
private static void doubleLockSingletonTest() {
//普通类可有多个实例
for (int i = 0; i < 100000; i++){
Thread thread = new Thread(new Runnable() {
public void run() {
LazyDoubleLockSingleton1 lazyDoubleLockSingleton1
= LazyDoubleLockSingleton1.getInstance();
}
});
thread.start();
}
//单例模式只有一个实例,变量共享,但方法可同时调用
for (int i = 0; i < 100000; i++){
Thread thread = new Thread(new Runnable() {
public void run() {
LazyDoubleLockSingleton2 lazyDoubleLockSingleton2
= LazyDoubleLockSingleton2.getInstance();
}
});
thread.start();
}
}
单例模式验证
数据验证
- 先创建普通类NormalClass
public class NormalClass {
private long times = 0;
public NormalClass(){
}
public void display(){
try{
System.out.println("normal class start, times = " + times);
times++;
Thread.sleep(60 * 1000L);
System.out.println("normal class end, times= " + times);
}catch (Exception e){
e.printStackTrace();
}
}
}
- 饿汉式创建单例SingleClass
public class SingleClass {
private int times = 0;
//饿汉模式创建单例
private static SingleClass singleClass = new SingleClass();
//将构造方法私有
private SingleClass(){}
public static SingleClass getInstance(){
return singleClass;
}
public void display(){
try{
System.out.println("single class start, times = " + times);
times++;
Thread.sleep(60 * 1000L);
System.out.println("single class end, times = " + times);
}catch (Exception e){
e.printStackTrace();
}
}
}
- 运行主程序,通过线程校验单例
public class Application {
public static void main(String[] args){
//普通类可有多个实例
for (int i = 0; i < 2; i++){
Thread thread = new Thread(new Runnable() {
public void run() {
NormalClass normalClass = new NormalClass();
normalClass.display();
}
});
thread.start();
}
//单例模式只有一个实例,变量共享,但方法可同时调用
for (int i = 0; i < 2; i++){
Thread thread = new Thread(new Runnable() {
public void run() {
SingleClass singleClass = SingleClass.getInstance();
singleClass.display();
}
});
thread.start();
}
}
- 运行结果如下,可以看到最终结果输出普通类创建的对象的数据是相互独立的,而单例模式的数据是相互影响的。但是只能证明数据是公用的,而不能证明只创建了一个对象。
对象验证
- 修改了客户端,直接校验对象的地址,使用 == 判断是否是同一个对象。
public class Application {
public static void main(String[] args) {
NormalClass normalClass1 = new NormalClass();
NormalClass normalClass2 = new NormalClass();
System.out.println(normalClass1 == normalClass2);
SingleClass singleClass1 = SingleClass.getInstance();
SingleClass singleClass2 = SingleClass.getInstance();
System.out.println(singleClass1 == singleClass2);
}
}
- 输出结果如下。最终证明数据是共用的,而且是属于同一个对象。
优缺点
优缺点 :
- 简单 : 一个类只有一个实例化对象,使用简单
- 节约资源 : 系统只需要初始化一个实例,节省资源
缺点 :
- 场景单一
- 难以实现复杂的功能
参考资料 :
https://www.cnblogs.com/damonhuang/p/5431866.html
https://blog.csdn.net/chenchaofuck1/article/details/51702129
https://blog.csdn.net/nsw911439370/article/details/50456231