标签:七种 private instance 实例 static 单例 写法 public
文章目录
前言
单例模式虽然是java的最基础最简单设计模式,但是里面也蕴藏了很多多线程的知识,深入理解单例模式对于理解多线程有很大的帮助
一、什么是单例模式?
示例:java的单例模式就是在一定的条件下,一个类只有一个实例存在,不存在多个实例。为了深入了解多线程在单例模式种的影响,下面将介绍七种单例模式的实现方式,前面5种是存在缺陷的。推荐使用后面两种。
二、七种单例模式
1.第一种
代码如下(示例):
public class SingletonObject1 {
/**
* can't lazy load.
*/
private static final SingletonObject1 instance = new SingletonObject1();
private SingletonObject1() {
//empty
}
public static SingletonObject1 getInstance() {
return instance;
}
}
这种单例写法,是典型的=饿汉模式,就是在类加载的时候就对类进行了实例化,缺点也就显而易见了:占用内存,因为你不知道这个实例在什么时候使用。
2.第二种
代码如下(示例):
public class SingletonObject2 {
private static SingletonObject2 instance;
private SingletonObject2() {
//empty
}
public static SingletonObject2 getInstance() {
if (null == instance)
instance = new SingletonObject2();
return SingletonObject2.instance;
}
}
这种写法采用了懒加载的方法,是懒汉模式,解决了第一种写法的缺点。但是在多线程模式下很可能会出现多个实例,也就不满足单例的条件了
3.第三种
public class SingletonObject3 {
private static SingletonObject3 instance;
private SingletonObject3() {
//empty
}
public synchronized static SingletonObject3 getInstance() {
if (null == instance)
instance = new SingletonObject3();
return SingletonObject3.instance;
}
}
这种写法是在第二种写法的基础上加上了synchronized关键字来给方法加锁,实现了线程安全。但是这里还是存在一个很严重的缺点:程序运行效率差,因为任何线程执行到getInstance方法,如果没有抢到锁都会被阻塞,不管这个类是不是已经被实例化了。我们希望在类被实例化之后每个线程执行到这个方法不会被阻塞额,直接取实例就可以了。于是就有了第四种写法。
4.第四种
public class SingletonObject4 {
private static SingletonObject4 instance;
private SingletonObject4() {
//---
}
//double check
public static SingletonObject4 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject4();
}
}
return SingletonObject4.instance;
}
}
这种写法就是常说的双重检查,这种写法大大地提高了程序的执行效率。在多线程情况下如果类已经被实例化,程序不会被阻塞,每个线程直接去取实例。但是这种写法还是有问题的,所以就有了第五种写法
5.第五种
public class SingletonObject5 {
private static volatile SingletonObject5 instance;
private SingletonObject5() {
//
Object obj = new Object()
}
//double check add volatile
public static SingletonObject5 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject5();
}
}
return SingletonObject5.instance;
}
}
这种写法是在第四种写法的基础上给instance属性加上volatile关键字,这个关键字能够保证程序执行的可见性和有序性。这样就解决了第四种的问题:java虚拟机jvm会自动为我们的代码进行优化,所以它可能会重排序我们程序的执行顺序。如上面代码示例的构造方法,如果没有虚拟机的优化,我们的代码执行顺序应该是先实例化玩obj,然后再示例化SingletonObject5 。但是,多线程情况下,经过虚拟机的优化,当一个线程示例化SingletonObject5 的时候,obj可能只是分配了地址,这个地址还没有指向任何实例。这时候另一个线程想要拿SingletonObject5 的实例,发现instance不为空,就拿去用了,obj还是null的。于是程序执行就出问题了。
给instance加上volatile关键字,能够保证程序执行的有序性,能够保证obj被实例化完成后,instance才被实例化。
6.第六种
public class SingletonObject6 {
private SingletonObject6() {
}
private static class InstanceHolder {
private final static SingletonObject6 instance = new SingletonObject6();
}
public static SingletonObject6 getInstance() {
return InstanceHolder.instance;
}
}
这种写法就显得比较优雅简洁了,使用静态内部类实现,无锁高效。这种只有在反射的情况下可以创建多个实例,正常情况下不会出现多例的情况。所以可以大方使用
7.第七种
public class SingletonObject7 {
private SingletonObject7() {
}
private enum Singleton {
INSTANCE;
private final SingletonObject7 instance;
Singleton() {
instance = new SingletonObject7();
}
public SingletonObject7 getInstance() {
return instance;
}
}
public static SingletonObject7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) {
IntStream.rangeClosed(1, 100)
.forEach(i -> new Thread(String.valueOf(i)) {
@Override
public void run() {
System.out.println(SingletonObject7.getInstance());
}
}.start());
}
}
这种写法使用枚举,应用了枚举唯一性。去实现单例。内部枚举是反射不能够穿透的,所以不能利用反射去创建实例。避免了多例情况的出现
标签:七种,private,instance,实例,static,单例,写法,public 来源: https://blog.csdn.net/madleep/article/details/120248606
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。