你知道吗?多个类多线程环境下静态构造函数的执行顺序

本文涉及的产品
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
简介: 调用A a=new A()请问输出是什么?为什么? class A { static A() { Stopwatch sw = new Stopwatch(); sw.

调用A a=new A()
请问输出是什么?为什么?

 
class A
{
    static A()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        XTrace.WriteLine("A1");
        Thread.Sleep(3000);

        //B b = new B();
        XTrace.WriteLine("AA");
        //ThreadPool.QueueUserWorkItem(delegate { XTrace.WriteLine("BB"); B b = new B(); });
        Thread thread = new Thread(new ParameterizedThreadStart(delegate { XTrace.WriteLine("BB"); B b = new B(); }));
        thread.Start();

        Thread.Sleep(3000);
        sw.Stop();
        XTrace.WriteLine("A2 " + sw.Elapsed);
    }

    public A() { XTrace.WriteLine("new A"); }
}
class B
{
    static B()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        XTrace.WriteLine("B1");
        Thread.Sleep(3000);

        A a = new A();

        Thread.Sleep(3000);
        sw.Stop();
        XTrace.WriteLine("B2 " + sw.Elapsed);
    }

    public B() { XTrace.WriteLine("new B"); }
}

 

 

关于静态构造函数函数的基本常识就不多说,园子里随处可见!

这个问题让群里的高手纠结了一整天,那个线程为什么不动?(线程等到A静态构造函数执行完毕后才执行)

 

傍晚时分,有人忍不住发信问微软:

Z_(164734xxx) 19:19:25
A static constructor is never called more than once, and it must be finished before any other thread can create an instance of the class or use a static member of the class. Therefore, the thread you try starting cannot start before A's static constructor ends.

 

 

网上很多资料说到静态构造函数,但是很少提到与线程相关的,这个例子实际上是想测试一下静态构造函数的多线程冲突。

 

其实,这个问题源自于XCode v7.3中一个隐秘的BUG。

实体类A的静态构造函数中可能会开一个线程去执行方法B,然后静态构造函数接着执行后续方法C,问题就在于B和C都会争夺同一个锁,如果B拿到这个锁,它会创建一个A的实例,但是因为A的静态构造函数正常执行C,C又等待B释放这个锁,从而形成了死锁,所有用到类型A的线程都会挂起。

因为B和C的执行速度不一样,要是C先拿到资源,就不会出现死锁,所以这个问题解决起来特别的麻烦!

 

XCode v7.3的这个BUG表明,那个线程应该是可以同步执行的,但是为什么测试项目里面线程就是不动呢?(先看看大家讨论,后面再公布答案)

 

附上XCode中出错的部分

/// <summary>
/// 数据实体类基类。所有数据实体类都必须继承该类。
/// </summary>
[Serializable]
public partial class Entity<TEntity> : EntityBase where TEntity : Entity<TEntity>, new()
{
    #region 构造函数
    /// <summary>
    /// 静态构造
    /// </summary>
    static Entity()
    {
        // 1,可以初始化该实体类型的操作工厂
        // 2,CreateOperate将会实例化一个TEntity对象,从而引发TEntity的静态构造函数,
        // 避免实际应用中,直接调用Entity的静态方法时,没有引发TEntity的静态构造函数。
        TEntity entity = new TEntity();
        EntityFactory.CreateOperate(Meta.ThisType, entity);
    }

 

TEntity就是实体类,它本身也有静态构造函数,并且它的静态构造函数里面会开一个线程去调用EntityFactory.CreateOperate(Type type),该方法会取得一个字典的锁,然后通过Activator.CreateInstance(type)创建类型type的实例,加入字典,也就是实体类本身的实例。

EntityFactory.CreateOperate(Type type, IEntityOperate entity)跟上面的EntityFactory.CreateOperate(Type type)共同点再也它也要那这个字典的锁,不同的在于它只是把entity加入字典。

结果就是:如果两个参数这个先执行,就没有问题,如果一个参数那个先执行,大家一起死!

 

答案:

上面微软的答复邮件说得很清楚,静态构造函数只会被调用一次,并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员!

这里面包含几层一次:

1,静态构造函数只会被调用一次,并且在所有对该类的访问之前。这一点我确信99.99%的人都知道。

2,“其它线程”。也就是说,只是其它线程不能创建实例和调用静态成员而已,当前线程仍然是可以的。

3,“创建实例或使用静态成员”。那么实例成员呢?当然不可能了,因为实例都无法创建,如何使用实例成员?

4,也是最隐秘的地方。测试代码中,在A的静态构造函数里面使用了匿名函数,而编译器会把它编译成为A的一个静态方法,因此,它就成了A的静态成员了,所以……

 

实际上,我们没注意到的地方是第四点,太粗心了!

不过,可能清楚第二点的人不到10%吧。

我不相信神话,我只相信汗水!我不相信命运,我只相信双手!
相关实践学习
基于OpenTelemetry构建全链路追踪与监控
本实验将带领您快速上手可观测链路OpenTelemetry版,包括部署并接入多语言应用、体验TraceId自动注入至日志以实现调用链与日志的关联查询、以及切换调用链透传协议以满足全链路打通的需求。
分布式链路追踪Skywalking
Skywalking是一个基于分布式跟踪的应用程序性能监控系统,用于从服务和云原生等基础设施中收集、分析、聚合以及可视化数据,提供了一种简便的方式来清晰地观测分布式系统,具有分布式追踪、性能指标分析、应用和服务依赖分析等功能。 分布式追踪系统发展很快,种类繁多,给我们带来很大的方便。但在数据采集过程中,有时需要侵入用户代码,并且不同系统的 API 并不兼容,这就导致了如果希望切换追踪系统,往往会带来较大改动。OpenTracing为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范。OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。Skywalking基于OpenTracing规范开发,具有性能好,支持多语言探针,无侵入性等优势,可以帮助我们准确快速的定位到线上故障和性能瓶颈。 在本套课程中,我们将全面的讲解Skywalking相关的知识。从APM系统、分布式调用链等基础概念的学习加深对Skywalking的理解,从0开始搭建一套完整的Skywalking环境,学会对各类应用进行监控,学习Skywalking常用插件。Skywalking原理章节中,将会对Skywalking使用的agent探针技术进行深度剖析,除此之外还会对OpenTracing规范作整体上的介绍。通过对本套课程的学习,不止能学会如何使用Skywalking,还将对其底层原理和分布式架构有更深的理解。本课程由黑马程序员提供。
相关文章
|
2月前
|
存储 JavaScript 前端开发
构造函数继承有什么缺点?
【10月更文挑战第26天】构造函数继承虽然能够实现属性的继承,但在方法继承、内存使用效率、访问父类原型属性以及实现多态性等方面存在一些缺点。在实际开发中,可以根据具体的需求和场景,结合其他继承方式来综合解决这些问题,以实现更高效、更灵活的继承机制。
85 52
|
3月前
|
Java
Java“非静态方法 ... 不能在静态上下文中被引用”解决
在Java中,“非静态方法……不能在静态上下文中被引用”的错误通常源于在静态方法中直接调用非静态方法。解决方法包括:1) 创建类的实例后调用;2) 将非静态方法改为静态方法;3) 重新评估和调整类的设计以避免此类问题。
555 1
|
8月前
|
监控 安全 Java
Java反射:深入了解动态类操作
Java反射:深入了解动态类操作
145 0
|
存储 安全 Java
一文解读类的加载过程(类的生命周期)(上)
一文解读类的加载过程(类的生命周期)
|
缓存 Java 开发者
一文解读类的加载过程(类的生命周期)(下)
一文解读类的加载过程(类的生命周期)(下)
|
SQL 设计模式 安全
【java基础】静态内部类又是如何实现线程安全的呢?(利用了类的加载机制)
【java基础】静态内部类又是如何实现线程安全的呢?(利用了类的加载机制)
257 0
|
存储 缓存 安全
类的加载机制以及类、对象初始化的详细过程
java类的生命周期包括加载、连接(验证、准备、解析)、初始化、使用、卸载五个阶段。初始化的顺序是怎样的呢?
153 0
类的加载机制以及类、对象初始化的详细过程
|
存储 Java 编译器
Java中方法调用分析!详细解析静态分派和动态分派的执行过程
本篇文章中介绍了Java中的方法调用以及方法的解析和执行过程。分析了在Java中方法的静态分派和动态分派的执行过程。最后分析了在虚拟机中静态分派和动态分派的实现方式。通过这篇文章的学习,可以帮助我们很好地认识Java中的方法的执行情况。
394 0
Java中方法调用分析!详细解析静态分派和动态分派的执行过程
|
Java
Java代码块(静态和非静态)在子父类中的执行顺序
Java代码块(静态和非静态)在子父类中的执行顺序
167 0

热门文章

最新文章

下一篇
开通oss服务