延迟初始化是一种常见的编程技术,直到第一次使用对象时才创建。通常会在操作实现中初始化对象,而不是在类构造函数中初始化。这种技术的主要优点是节省内存,因为只创建执行应用所需的必要对象。在一个类中肯能要声明大量对象,但每次程序执行中不会用到所有对象,所以不需要为程序执行中未使用的对象提供内存。这种优势在资源有限的环境中非常有用。
相比之下,因为在操作中需要首次创建对象,所以这种技术的缺点就是应用的性能问题。
在并发应用程序中使用此技术,也会引发问题。由于多个线程能够同时执行一个操作,因此它们可以同时创建一个对象,这种情况可能会有问题。这对于单例类来说具有特殊的重要性。如前所述,应用只有这些类中的一个对象,而并发应用可以创建多个对象。查看如下代码:
public static DBConnection getConnection(){
if (connection==null) {
connection=new DBConnection();
}
return connection;
}
这是单例类中,使用延迟初始化的典型方法,得到应用中存在类的唯一对象的引用。如果对象尚未创建,则创建对象,最后返回自己。
本节将优雅的实现处理延迟初始化问题的解决方案。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为DBConnectionOK的类:
public class DBConnectionOK {
-
声明私有构造函数,输出执行的线程名称:
private DBConnectionOK() { System.out.printf("%s: Connection created.\n",Thread.currentThread().getName()); }
-
声明名为LazyDBConnectionOK的私有静态类,包含名为INSTANCE的private static final DBConnectionOK实例:
private static class LazyDBConnectionOK { private static final DBConnectionOK INSTANCE = new DBConnectionOK(); }
-
实现getConnection()方法,不接收任何参数且返回DBConnectionOK对象,返回INSTANCE对象:
public static DBConnectionOK getConnection() { return LazyDBConnectionOK.INSTANCE; } }
-
创建名为Task的类,指定其实现Runnable接口。实现run()方法,调用DBConnectionOK()方法的getConnection()方法:
public class Task implements Runnable{ @Override public void run() { System.out.printf("%s: Getting the connection...\n", Thread.currentThread().getName()); DBConnectionOK connection=DBConnectionOK.getConnection(); System.out.printf("%s: End\n", Thread.currentThread().getName()); } }
-
实现本范例主类,创建名为Main的类,包含main()方法:
public class Main { public static void main(String[] args) {
-
创建20个Task对象和执行它们的20个线程:
for (int i=0; i<20; i++){ Task task=new Task(); Thread thread=new Thread(task); thread.start(); } } }
工作原理
范例的关键是getConnection()方法和private static class LazyDBConnection实例。当第一个线程调用getConnection()方法时,LazyDBConnectionOK类通过调用DBConnectionOK类构造函数初始化INSTANCE对象。对象通过getConnection()方法返回,当剩余线程调用getConnection()方法时,已经创建完对象,因此所有线程使用只创建一次的相同对象。
当运行本范例时,会看到20个任务的起始和结束信息,但只有一条创建信息。