实现单例模式的五种方式
一.什么是单例模式
- 单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。
- 优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
- 缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
二.用饿汉模式实现单列模式
- 饿汉模式:顾名思义十分着急,这里的饿汉模式是指实例的创建时机早,实例在类加载的时候就创建了,相当于程序一启动,实例就创建好了.
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
代码实例:
class Singleton{
private static Singleton singleton = new Singleton();
public static Singleton getSingleton(){
return singleton;
}
private Singleton(){};
}
public class Test1 {
public static void main(String[] args) {
Singleton s1 = Singleton.getSingleton();
Singleton s2 = Singleton.getSingleton();
System.out.println(s1==s2);
}
}
运行结果:
三.用懒汉模式实现单例模式
- 懒汉模式:故名思意不着急,此处是指,创建现成的时机是你需要的时候,它才创建.
这个代码需要注意两个点:
- 1).线程安全问题.如果不加锁就可能创建出两个线程.
2).指令重排序引起的线程安全问题.
nstance = new SingletonLazy; 这个代码其实可以拆成三个大步骤(不是三个命令)
①.申请一段内存空间 ②.在这个内存上调用构造方法,创建出实例. ③把这个内存地址赋给instance引用变量正常情况下,上述代码是按照123的顺序来执行的.但是编译器也可能优化为132的顺序来执行.对于单线程来讲上述两种执行过程并不会有影响.但对于多线程来说.如果t1线程执行完13.此时instance的指向就不为null了,是一个非空但未实例化的对象,此时如果t2线程调度到执行,到判断是否加锁语句时就不会执行,而是直接返回.如果t2继续使用instance里面的属性和方法就会出现问题(此时属性都是未初始化的全"0"值).就可能引起代码的逻辑出现问题. 解决这个问题需要引入volatile关键字,解决指令重排序的问题
代码实例:
class SingletonLazy{
private volatile static SingletonLazy singletonLazy = null;
public static Object locker = new Object();
public static SingletonLazy getSingletonLazy(){
synchronized (locker) {
//保证线程安全
if (singletonLazy == null) {
//判断是否要创建对象
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
private SingletonLazy(){
}
}
public class Test2 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getSingletonLazy();
SingletonLazy s2 = SingletonLazy.getSingletonLazy();
System.out.println(s1==s2);
}
}
运行结果:
此时会出现一个优化的点,就是在保证线程安全的前提下,实例被创建出来以后,每次获取实例的时候都需要再次加锁判断,这样会减低代码的执行效率.故此引出下一种创建单例模式的方法—双重校验锁.
四.用双重校验锁实现单例模式
优点:线程安全;延迟加载;效率较高。
代码实例:
package Demo2;
class SingletonLazy{
private volatile static SingletonLazy singletonLazy = null;
public static Object locker = new Object();
public static SingletonLazy getSingletonLazy(){
if(singletonLazy==null) {
//判断是否要加锁
synchronized (locker) {
if (singletonLazy == null) {
//判断是否要创建对象
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
private SingletonLazy(){
}
}
public class Test2 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getSingletonLazy();
SingletonLazy s2 = SingletonLazy.getSingletonLazy();
System.out.println(s1==s2);
}
}
运行结果:
五.用静态内部类实现单列模式
静态内部类实现单例模式保证线程安全的方法和饿汉模式一样,类的静态属性只会在第一次加载类的时候初始化.所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
代码实例:
//使用静态内部类实现单例模式--线程安全
class SingletonStaticInner {
private SingletonStaticInner() {
}
private static class SingletonInner {
private static final SingletonStaticInner singletonStaticInner = new SingletonStaticInner();
}
public static SingletonStaticInner getInstance() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return SingletonInner.singletonStaticInner;
}
}
public class Test3 {
public static void main(String[] args) {
SingletonStaticInner singletonStaticInner1 = SingletonStaticInner.getInstance();
SingletonStaticInner singletonStaticInner2 = SingletonStaticInner.getInstance();
System.out.println(singletonStaticInner1==singletonStaticInner2);
}
}
运行结果:
六.用枚举类实现单例模式
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
public enum Singleton {
INSTANCE;
private Singleton() {}
}