单例模式,顾名思义,就是无论采用何种方式去创建,都要确保只能创建出一个对象出来。
- 1 饿汉模式:类被加载的时候就去创建对象,典型的以空间换时间,故不存在线程安全问题;但创建出来的对象有可能不被用到,就失去了延迟创建的好处。
public class Student {
private static Student instance = new Student();
private Student(){}
public static Student getInstance()
{
return instance;
}
}
- 2 饱汉模式:用对象的时候再去创建,具备了延迟创建的好处,但在用户第一次使用这个对象的时候要忍受创建对象的时间,省了空间,费了时间;且解决了一定的线程安全的问题。
public class Student {
private static Student instance = null;
private Student(){}
public static synchronized Student getInstance() {
if(instance == null){
instance = new Student();
}
return instance;
}
}
给getInstance方法加synchronized的原因在于保证任何一个瞬间只能有一个线程进来创建对象,但这样的加锁方式太重。
- 3 double check:双重检查,轻量级加锁方式
public class Student {
private static Student instance = null;
private Student(){}
public static Student getInstance() {
if(instance==null)//避免每次进来都先加锁
{
synchronized (instance)
{
if(instance == null)//确保没有对象才去执行创建对象语句
{
instance = new Student();
}
}
}
return instance;
}
}
- 4 双重加锁方式也只是解决了大部分的线程安全问题,但因为以下的原因:
(1) 编译器优化了程序指令, 以加快cpu处理速度.
(2) 多核cpu动态调整指令顺序, 以加快并行运算能力.
可能出现以下的问题:
(1)线程A, 发现对象未实例化, 准备开始实例化
(2)由于编译器优化了程序指令, 允许对象在构造函数未调用完前, 将共享变量的引用指向部分构造的对象, 虽然对象未完全实例化, 但已经不为null了.
(3)线程B, 发现部分构造的对象已不是null, 则直接返回了该对象.
解决这个问题,需要指令不会因编译器的优化而省略,且要求每次直接读值。volatile关键字就能做到这件事情。所以,代码优化为如下:
public class Student {
private volatile static Student instance = null;
private Student(){}
public static Student getInstance() {
if(instance==null)//避免每次进来都先加锁
{
synchronized (instance)
{
if(instance == null)//确保没有对象才去执行创建对象语句
{
instance = new Student();
}
}
}
return instance;
}
}
- 5 使用静态内部类
public class Student{
private Student(){}
private static class SingleTonHolder{
private static Student instance = new Student();
}
public static Student getInstance(){
return SingleTonHolder.instance;
}
}
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存,而当Student第一次被加载时,并不需要去加载SingleTonHolder,只有当getInstance()方法第一次被调用时,才会去初始化instance,第一次调用getInstance()方法会导致虚拟机加载SingleTonHolder类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化,但静态内部类单例没发传递参数。
- 6 使用枚举完成单例
public class TestEnum{
public static void main(String[] args) {
Student student1 = StudentEnum.SINGLETON.getInstance();
Student student2 = StudentEnum.SINGLETON.getInstance();
System.out.println(student1 == student2);
}
}
enum StudentEnum {
SINGLETON;
private Student student = null;
private StudentEnum(){
student = new Student();
}
public Student getInstance(){
return student;
}
}
class Student{
}
以上就是实现单例的多种方式。
网友评论