延迟加载:“懒汉模式”
延迟加载是指在调用getInstance()方法时创建实例。常见的方法是在getInstance()方法中实例化new。实现代码如下:
但是因为getInstance()中有多个语句,所以可能存在线程安全问题。运行结果还表明:
2041531420
1348345633
1348345633
即使getInstance()中有更多的语句,也会有三个不同的对象。线程。.(3000)被添加到if(myObject==null)语句块。结果表明:
1 218620763
2 58615885
3 712355351
解决方案:DCL
如果使用synchronized关键字锁定整个getInstance()或整个if语句块,则会存在效率问题。
最后,采用DCL(double-Check Locking)双校验锁定机制,这也是大多数多线程结合单例模式的解决方案。第一层主要是为了避免不必要的同步,第二层是在空情况下创建实例。
测试结果,得到的是相同的hashcode。
立即加载:“饿汉模式”
立即加载意味着在使用类时创建了对象。常见的实现方法是直接新实例化。也就是说,在调用方法之前,创建实例。示例代码如下:
1 class MyObject {
2 private static MyObject myObject=new MyObject();
3 private MyObject(){}
4 public static MyObject getInstance(){
5 //如果还有其他代码,存在线程安全问题
6 return myObject; 7 } 8 } 9 class MyThread extends Thread{ 10 @Override 11 public void run() { 12 System.out.println(MyObject.getInstance().hashCode()); 13 } 14 } 15 public class Run { 16 public static void main(String[] args) { 17 MyThread t1=new MyThread(); 18 MyThread t2=new MyThread(); 19 MyThread t3=new MyThread(); 20 t1.start(); 21 t2.start(); 22 t3.start(); 23 } 24 }
运行结果如下:
可以发现,实现了单例模式,因为多个线程得到的实例的hashCode是一样的。
静态内置类
采用静态内置类的方法,是线程安全的。
使用static代码块
静态代码块的代码在重用类时执行,因此可以应用静态代码块的这个特性来实现单例设计模式。
使用enum枚举数据类型
当使用枚举类时,类似于静态代码块,自动调用构造函数。枚举由javac编译,并转换为诸如公共最终类T extends Enum之类的定义。也就是说,我们定义的枚举在第一次真正使用时由虚拟机加载和初始化,并且初始化过程是线程安全的。众所周知,解决单例并发问题的主要方法是初始化过程中的线程安全问题。
因此,由于枚举的上述特性,枚举实现了固有的线程安全的单例。同时,枚举可以解决反序列化会破坏单例的问题。
enum MyObject{
INSTANCE;
}
SimpleDataFormat
SimpleDataFormat使用带有线程安全问题的单例模式。SimpleDateFormat中的日期格式不同步。建议(建议)为每个线程创建单独的格式实例。如果多个线程同时访问一种格式,则它必须保持外部同步。
解决方案1:需要的时候创建新实例
在需要SimpleDateFormat的地方创建新实例可以通过在任何时候将线程安全的对象从共享更改为本地私有来避免多线程,但是它也增加了创建对象的负担。一般来说,对性能的影响不是很明显。
解决方案2:同步SimpleDateFormat对象
当有更多的线程时,当一个线程调用此方法时,其他想要调用此方法的线程需要块。当多线程并发性较大时,会对性能产生一定的影响。
解决方案3:使用ThreadLocal
ThreadLocal还用于将共享变量转换为独占变量。ThreadLocal肯定比方法独占性减少了在并发环境中创建对象的开销。如果性能要求很高,通常建议使用这种方法。