`
RednaxelaFX
  • 浏览: 3052968 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

静态变量的初始化,你是否真的需要lazy?

阅读更多
老赵几天前提到double-check,昨天又提到属性的lazy加载,想起记这么一点:
在实现单例的时候,你是否真的需要用double-check之类的技巧来实现lazy创建实例?

使用单例模式但又想让它lazy创建实例,理由通常是“实例的创建可能伴随一些复杂的初始化计算,或者需要持有一些外部资源之类;如果程序中并没有使用到那个实例,那么付出的代码就白费了”。
但很多时候上述理由是个伪命题:不使用的类根本就不会被初始化。既然没有初始化,就不会付出代价去创建单例的实例。

Effective Java, 2nd推荐在Java 5或更新的版本中使用enum来实现Singleton模式。那看看这段代码的输出:
public class Program {
    public static void main(String[] args) {
        System.out.println("main()");
        System.out.println("leaving main()");
    }
}

enum Singleton {
    INSTANCE;
    Singleton() {
        System.out.println("Singleton()");
    }
}

结果是:
引用
main()
leaving main()

虽然定义了Singleton类型,但程序中并没有使用它,所以也就没有引发它的初始化。
那么在main()里添加一行:
public class Program {
    public static void main(String[] args) {
        System.out.println("main()");
        Singleton s = Singleton.INSTANCE;
        System.out.println("leaving main()");
    }
}

enum Singleton {
    INSTANCE;
    Singleton() {
        System.out.println("Singleton()");
    }
}

结果变为:
引用
main()
Singleton()
leaving main()

哪儿用哪儿才初始化。

只要实现Singleton的时候,该类上没有别的对外公开的静态变量或者静态方法,外界想要使用Singleton实例只能通过INSTANCE的话,那引发Singleton初始化的只有对INSTANCE的访问(这时初始化是正确的)或者通过反射(例如Class.forName(),这是例外情况)。
要是有信心代码里不会出现“例外情况”的话,又何必费神去实现double-check呢?

C#里则有些细微差异。某个类的静态初始化“想当然”也应该是初次使用的时候才进行,但实际上这取决于静态初始化逻辑是怎么写的。如果写成:
using System;

public class Singleton {
    public static Singleton Instance = new Singleton();
    private Singleton() {
        Console.WriteLine("Singleton()");
    }
}

static class Program {
    static void Main(string[] args) {
        Console.WriteLine("Main()");
        Console.WriteLine("leaving Main()");
    }

    static void Foo() {
        var s = Singleton.Instance;
    }
}

虽然代码中有Foo()使用了Singleton,但实际执行路径上没有经过Foo(),其它地方也没用过Singleton类,于是输出结果是:
引用
Main()
leaving Main()

但要是在Main()里添加一行:
using System;

public class Singleton {
    public static Singleton Instance = new Singleton();

    private Singleton() {
        Console.WriteLine("Singleton()");
    }
}

static class Program {
    static void Main(string[] args) {
        Console.WriteLine("Main()");
        var s = Singleton.Instance;
        Console.WriteLine("leaving Main()");
    }

    static void Foo() {
        var s = Singleton.Instance;
    }
}

输出结果跟想像的可能就不一样了:
引用
Singleton()
Main()
leaving Main()

这是因为C#中直接在静态变量声明的地方就初始化,而且没有显式提供静态构造器实现的话,会使类带上beforefieldinit标记,使得类的静态初始化提早执行。稍微改改,给Singleton类添加一个空的静态构造器的话……
using System;

public class Singleton {
    public static Singleton Instance = new Singleton();

    static Singleton() {
    }

    private Singleton() {
        Console.WriteLine("Singleton()");
    }
}

static class Program {
    static void Main(string[] args) {
        Console.WriteLine("Main()");
        var s = Singleton.Instance;
        Console.WriteLine("leaving Main()");
    }

    static void Foo() {
        var s = Singleton.Instance;
    }
}

会发现执行结果变为:
引用
Main()
Singleton()
leaving Main()

这种写法就不会使类带上beforefieldinit,于是初始化时间就跟“想像中”的一样,哪儿用到哪儿才初始化。把Instance的初始化整个挪到静态构造器里的结果也一样。

有时候费了力气去写个double-check搞不好还写错了,要是回头发现其实不用自己费神写lazy逻辑也能达到效果的话,那肯定郁闷坏了。引用老赵的帖的标题:如果是能简单解决的问题,就不用想得太复杂了

^ ^
分享到:
评论
4 楼 mercyblitz 2010-06-23  
这是ClassLoader机制加载的,记载类的时候就synchronized啦,因此不会出现重复加载或者不可见的情况。
3 楼 RednaxelaFX 2009-09-06  
Saito 写道
http://crazybob.org/2007/01/lazy-loading-singletons.html

    Guice 这边还有一种 singleton . 不过不知道在c#上是不是也适用.. 

嗯我知道这个办法。C#里应该也可以用吧,原理差不多。不过我没测试过,回头试试。
2 楼 Saito 2009-09-06  
   睡糊涂了..  url 点成 quote 了..
1 楼 Saito 2009-09-06  
引用
http://crazybob.org/2007/01/lazy-loading-singletons.html


    Guice 这边还有一种 singleton . 不过不知道在c#上是不是也适用.. 

相关推荐

    lazy-static.rs:一个用于在Rust中定义惰性求值静态变量的小宏

    使用此宏,可能具有static ,这些static要求在运行时执行代码才能进行初始化。 这包括需要堆分配的任何内容,例如向量或哈希图,以及需要计算非const函数调用的所有内容。最小支持的rustc 1.27.2+ 此版本已在CI中...

    lazy-static.rs, 在 Rust 中,用于定义惰性计算的static 变量的小宏.zip

    lazy-static.rs, 在 Rust 中,用于定义惰性计算的static 变量的小宏 lazy-static.rs在 Rust 中声明延迟求值的静态的宏。使用这里宏,可以以使 static s 在运行时要求执行代码,以便初始化。 这包括需要堆分配,如...

    有关static block静态代码块和单态设计模式

    3. **初始化**:最后,JVM执行类的初始化,包括激活类的静态变量初始化和静态代码块。此时,类的静态成员会被真正地初始化为它们的指定值。 单态设计模式(Singleton Pattern)是一种常见的软件设计模式,它的目标...

    第五章 初始化与清理

    例如,通过延迟初始化(Lazy Initialization),可以在需要时才创建对象,减少启动时的资源消耗。而及时清理则能避免资源浪费,提高系统的响应速度。 博文链接提到的iteye博客,可能提供了具体的代码示例和实践经验...

    单例模式中声明静态自己类型的指针编译显示未定义处理

    如果在头文件中只声明了静态成员变量,而没有在cpp文件中进行初始化,编译器会认为这是一个外部定义,需要在链接阶段找到对应的定义。如果找不到,就会出现“未定义的引用”错误。 针对这个错误,我们可以采取以下...

    怎样提高代码效率(Java).do

    例如,在调用类的构造函数时,Java 会把变量初始化成确定的值。 5. 使用大写的 SQL 语句 在 JAVA + ORACLE 的应用系统开发中,java 中内嵌的 SQL 语句尽量使用大写的形式,以减轻 ORACLE 解析器的解析负担。 6. ...

    16Lazy,函数,并行

    2-创建一个静态私有变量数据类型是当前类的数据类型且不进行初始化 3-提供一个公有静态的获取当前对象的方法 4-进行判断, 若当前对象没有被创建, 创建对象, 否则返回 object LazyDemo extends App { def init():...

    java代码效率优化.docx

    Java 中的变量初始化需要花费时间和空间,避免重复初始化变量可以提高性能。 5. 在 JAVA + ORACLE 的应用系统开发中,java 中内嵌的 SQL 语句尽量使用大写的形式,以减轻 ORACLE 解析器的解析负担。 使用大写的 ...

    Once_cell:Rust库,用于单个分配单元和无宏的惰性静态

    这意味着你可以为每个`Lazy<T>`实例指定一个初始化闭包,这个闭包将在首次访问时运行。 在实际使用中,Once_cell库常用于以下场景: 1. 全局配置:当需要在程序运行期间只初始化一次的全局配置时,可以利用Once_...

    DCL常用设计方法

    基本思想是在初始化单例对象时,进行两次检查:第一次在不加锁的情况下判断实例是否已经创建,若未创建,则进入同步代码块进行第二次检查。这样可以避免不必要的同步开销,提高程序效率。然而,原始的DCL在Java早期...

    单例模式.zip(c#设计模式)

    2. 懒汉式(Lazy Singleton):首次调用时初始化,延迟加载,但需要考虑线程同步问题。 ```csharp public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy(() => new Singleton...

    c#单例3种实现

    懒汉式(静态变量) 这种方式是在第一次调用`Instance`时才创建实例,但不保证线程安全。为确保线程安全,可添加`lock`关键字。 ```csharp public sealed class Singleton { private static Singleton instance;...

    java代码性能问题检查计划及方案

    - 避免重复初始化变量:重复初始化会浪费资源,应当确保变量只在需要时初始化。 - JAVA + ORACLE整合:优化数据库查询,使用预编译语句,减少网络传输和解析时间。 - I/O流操作:合理使用缓冲区,避免频繁的读写...

    winfrom单例模式

    但是,由于WinForms窗体需要在UI线程上初始化,因此通常会在窗体类的静态方法中初始化实例。 3. **公共访问点**:提供一个公共的静态方法,如`GetInstance()`,用于获取窗体的唯一实例。 ```csharp public static ...

    iOS单例代码

    2. 在实现文件中,我们使用`@synchronized`关键字确保线程安全,同时初始化静态实例变量。 ```objc @implementation Singleton static Singleton * _sharedInstance = nil; + (instancetype)sharedInstance { @...

    java代码-DynInit

    3. **延迟初始化**:Java允许我们使用`lazy initialization`策略,这意味着一个变量或资源只有在真正需要时才会被初始化。这通常通过使用`null`检查或双重检查锁定(Double-Checked Locking)来实现,以提高性能和...

    java代码与编程题

    3. 初始化父类的非静态成员和非静态初始化块。 4. 调用父类的构造函数。 5. 初始化子类的非静态成员和非静态初始化块。 6. 调用子类的构造函数。 在给定的示例代码中,当执行`ChildClass cc = new ChildClass();`时...

    【独家高薪面试题库】与【实战配套练习演练】5.类构造函数与实例化1

    理解这些默认值对于避免在编写程序时出现意外行为至关重要,特别是在处理大量数据或者初始化变量时。 总的来说,Singleton类的使用可以确保资源的有效管理和控制,构造函数则是创建和初始化对象的关键工具,而掌握...

    JAVA中单例模式的几种实现方式.doc

    Lazy Initialization Holder Class(延迟初始化持有类) 另一种实现线程安全的单例模式的方法是使用静态内部类。这种方式利用了Java类加载机制的特性来保证初始化的线程安全性,同时又延迟了单例的初始化过程。 `...

Global site tag (gtag.js) - Google Analytics