一. 定义:
一种最最常见的一种模式,保证整个程序中只有一个实例
主要思想: 保证程序只有一个实例就可以了,不要仅限于目前主流的写法,如果没有更好的办法,那就只能用目前主流的方法了。
二. 写法套路
1. 构造函数私有,防止在外部 new 对象
2. 内部必须提供一个静态的方法,让外部调用
3. 线程安全问题.
三. 目前常见的写法
1.饿汉式
/**
* liys 2019-01-11
* 饿汉式
*/
public class Single1 {
private static Single1 instance = new Single1();
// static{ //这样写也可以
// instance = new Single1();
// }
private Single1() {}
private static Single1 getInstance(){
return instance;
}
}
优点: 写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点: 在类装载的时候就完成实例化。如果从始至终从未使用过这个实例,则会造成内存的浪费。例如: A和B页面打开的时候才会用到, 用户从未打开A和B页面, 这样就会造成内存的浪费.
2. 懒汉式(同步锁DCL)
/**
* liys 2019-01-11
* 懒汉式 (DCL)
*/
public class Single2 {
private static volatile Single2 instance;
private Single2() {}
private static Single2 getInstance(){
if(instance == null){ //保证效率
synchronized(Single2.class){ //保证线程安全
if(instance == null){
instance = new Single2();
}
}
}
return instance;
}
}
优点: 用到的时候才初始化对象, 效率高, 不会造成内存浪费.
缺点: 容易造成线程安全问题, 并发效率低.
注意: volatile的使用, 后面会单独讲.
3. 静态内部类
/**
* liys 2019-01-11
* 静态内部类
*/
public class Singleton {
private Singleton() {}
private static class SingletonHolder{
private static volatile Singleton INSTANCE = new Singleton();
}
private static Singleton getInstance(){
return SingletonHolder.INSTANCE ;
}
}
优点: 集合了饿汉式和懒汉式的优点,效率高, 线程安全, 占用内存少.
缺点: 无法传递参数.
4. 枚举
据说占内存会比较多,了解的不深, 不做介绍.
四. volatile关键字
作用:
1. 防止重排序
我们先看new对象的时候jvm做了什么? 例如:
instance = new Single2();
这行代码,其实在jvm里面的执行分为三步:
1.在堆内存开辟一块内存空间。
2.在堆内存中实例化Single2里面的各个参数。
3.把对象指向堆内存空间。
正常的执行顺序应该是1.2.3. 但是jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3.
例如: 线程A执行了1.3, 这个时候切换线程B, 这时候instance 已经不是null了, 所以线程B直接拿instance 来用, 这个时候就会出现问题.
加了volatile关键字, 执行顺序肯定就是1.2.3.
2. 线程的可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
instance = new Single2();
例如: 线程A初始化了instance , 这个时候切换到线程B了, 如果不加volatile关键字的话, 线程B有可能认为instance还是null, 因为每一个线程都有自己的缓存区, 线程A会先把instance 存到缓存区, 然后才去给主存里面赋值. 而B直接读取主存中的instance 所以有可能出现null.
这就是可见性问题,线程A对变量instance 修改了之后,线程B没有立即看到线程A修改的值。
这里只是做了简单的介绍, 想要了解的更深入,可以参考:https://www.cnblogs.com/dolphin0520/p/3920373.html
网友评论