`
王世纪
  • 浏览: 17200 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

从死锁问题说起

    博客分类:
  • java
阅读更多
前几天发生了一个枚举死锁问题,下面分析下,同时将java初始化话进行整理下

 

一:枚举死锁 问题

在讨论上面这个问题之前,先熟悉下什么情况会触发java类的初始化。

参考jvm规范,java虚拟机实现都必须在类,接口首次被主动使用时进行初始化,那什么情况是主动使用,以下几种情形符合主动使用的要求。

  •  执行以下java指令的时候,newgetstaticputstatic, or invokestatic。也就是常说的new对象, 获得static属性,设置static属性,和调用static方法。
  • 第一次MethodHandle实例的调用,相关指令为REF_getStaticREF_putStaticREF_invokeStatic
  • 调用类,库中的某些特定方法,比如类Class  或者包 java.lang.reflect中的某些反射方法。
  • 初始化 子类的时候,会首先初始化父类
  • 当虚拟机启动的某个被标明为启动类的类(即含有main()方法的类)

    参考http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5 ,jvm规范同时要求java虚拟机实现必须确保初始化过程被正确的同步,如果多个线程同时初始化一个类,仅仅允许其中的一个线程执行初始化,其他的线程必须等待,当初始化完成后,通知其他等待的线程完成初始化。

死锁代码为 

从代码中,可以看出在执行populateNames方法的时候,会首先得到enumClass作为对象的锁,然后进行里面的操作,在内部,会通过反射的方式,得到相应的静态属性,根据前面的初始化时机的描述,会发现在这里 如果相应的类没有被初始化,就会执行类初始化。
想象一下此时如果另一个 线程正在执行这个类的初始化,并且初始化的过程中会调用到这个populateNames 方法,就会死锁的情况。
步骤如下:
  • 线程A 先执行populateNames 方法,线程B进行enumClass 的初始化
  • 线程A在方法内部,获取enumClass静态属性值,触发类的初始化,因为线程B正在初始化当前类,因此线程A会等待线程B初始化的完成(多线程初始化类时,只能有一个线程初始化,其他线程等待这个线程初始化的完成)
  • 线程B在初始化的过程 中,会调用populateNames 方法,由于populateNames 方法内的对象锁,已经被线程A拿到,线程B拿不到此对象锁,因为会等待线程A对该锁的释放。
  • 以上可以看到线程A等待线程B的初始化,而线程B等待线程A执行完populateNames 方法,造成死锁。

解决方案是将populateNames 方法中的synchronized给去掉,这个解决方案可以继续讨论。在跟踪这个代码的时候,发现这个类型的死锁问题这个包之前也出现过,看下EnumUtil.getEnumType 的代码

 

可以看到 之前也出现了 这个问题。

 

类似的问题在这两篇文章中也有说明Spring 3.1.4之前版本中一个deadlock bug诡异的http请求返回499的case排查

 

和枚举问题相似 下面这两段代码也有问题,在看下面的分析之前,可以自己分析下到底什么问题。

这两段代码会造成执行等待(不能完成正常的执行流程),代码的执行过程大体相同

1、首先主线程M获取初始化锁,设置一个标志位表示初始化正在进行。

2、首先完成对静态变量的初始化,然后进入static块,启动另一个线程N。

3、在线程N中会调用类的静态变量,这个时候看到那个标志位(还没有初始化完成),就会等待主线程初始化完成

4、由于主线程M的初始化包含 静态代码块的初始化,因此主线程的初始化会等待静态代码块的初始化完成(而代码中的while循环,和join方法都是要保证线程N的执行完成,才会向下执行)

 

 

这种问题的排查 可、可以使用jstack看下,很容易找到问题所在,当然真正的故障排查的困难在于确认是死锁的问题,咱们现在是在倒果为因的排查,简单了些。以StaticThreadInit1为例

"Thread-0" prio=6 tid=0x02530400 nid=0x1980 in Object.wait() [0x04b8f000] 
java.lang.Thread.State: RUNNABLE 
at StaticThreadInit1$1.run(StaticThreadInit1.java:7) 
Locked ownable synchronizers: 
- None 

"main" prio=6 tid=0x002b9400 nid=0x17a8 runnable [0x0025f000] 
java.lang.Thread.State: RUNNABLE 
at StaticThreadInit1.<clinit>(StaticThreadInit1.java:10) 
Locked ownable synchronizers: 
- None

可以看到 另起的线程,名称为Thread-0 被锁定,而main 也被同样的锁锁住,并且一直在代码中的第10行执行,第10行代码就是 while循环。因此不能正常执行。

二:java的初始化

上面的枚举问题隐含着两个方面的内容

一:多个线程对同一个java类,接口执行初始化操作的时候,会进行同步,只有一个线程得到初始化的机会,其他线程只能进行等待,这正好是synchronized的使用场景,也在上面的枚举死锁问题得到了验证

二:静态属性值得获取会触发java初始化,同时不能正常执行的两个例子告诉我们 初始化的时候包含静态属性和静态代码块的初始化,那java的初始化到底包含哪些方面的内容呢?java的初始化又是按照什么顺序进行的初始化呢?下面说明这个问题。

 

上图展示了类的生命周期流向,本文仅仅看类的初始化和对象初始化两个阶段

类初始化,它是一个类或接口被首次使用的前阶段中的最后一项工作,本阶段负责为类变量赋予正确的初始值。Java 编译器把所有的类变量初始化语句和类型的静态初始化器通通收集到 <clinit> 方法内,该方法只能被 Jvm 调用,专门承担初始化工作。

对象初始化Java 编译器在编译每个类时都会为该类至少生成一个实例初始化方法--即 "<init>()" 方法。此方法与源代码中的每个构造方法相对应,如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 "<init>()" 方法.

上面还有一个阶段比较重要,就是准备阶段,java虚拟机装载了一个类文件,并做了一些相应的验证之后,就进入了准备阶段,在准备阶段,java虚拟机会为类变量分配内存,设置默认初始值,但在到达初始化阶段之前,这个值不是真正的初始值,仅仅是根据变量类型设置为默认值,比如 int 0 long  0L char ‘、u0000’,对象设置为null,boolean设置为为false。

类初始化有两个步骤

1)先确认是否存在父类,如果父类没有被初始化,先初始化父类。

 2)类中是否有类初始化方法,如果有,调用此方法进行初始化。

<clinit> 并不会显示的调用父类的<clinit> 方法,而仅仅是通过java虚拟机保证在执行子类的这个方法之前,父类的<clinit> 方法已经被执行。也就是说子类的静态初始化语句一定在父类的静态初始化语句之后调用

对象初始化,init方法可能包含三个部分的代码

1)一个父类<init>方法的调用

2)实例变量初始化的代码

3)构造方法体中的代码

上面这些太枯燥了,下面看下具体的代码。

                       

上面的代码执行下,然后分析下背后的原因。

如果有不明白的,可以看下 下面几篇文章,结合我上面的说明,应该就差不多了。

java类的初始化顺序java对象初始化顺序最权威的JLS初始化说明

 

最后:  

上面的枚举问题 简单一点就是一个牵涉到java初始化的 死锁问题。前文简单分析了下死锁的原因,同时将java初始化进行了一次的梳理。

分享到:
评论

相关推荐

    操作系统实验六 死锁问题实验

    操作系统实验六:死锁问题实验报告。通过本实验观察死锁产生的现象,考虑解决死锁问题的方法。从而进一步加深对于死锁问题的理解。掌握解决死锁问题的几种算法的编程和调试技术。练习怎样构造管程和条件变量,利用...

    车辆行驶死锁问题

    车辆行驶死锁问题,在Linux下用C语言完成下面模型:设有一个T型路口,其中A,B,C,D各处可容纳一辆车,车型方向如图所示。找出死锁并用有序分配法消除之,要求资源编号合理。

    哲学家就餐问题与死锁问题

    操作系统死锁问题 C语言实现 有详细代码 都能实现

    操作系统死锁问题

    进程死锁的检测:资源分配图的化简判断是否有死锁发生

    解决ORACLE死锁问题

    一、数据库死锁的现象 程序在执行的过程中,点击确定或保存按钮,程序没有响应,也没有出现报错。 二、死锁的原理 当对于数据库某个表的某一列做更新或删除等操作,执行完毕后该条语句不提 交,另一条对于这一列...

    关于Oracle数据库死锁问题的研究与讨论

    关于Oracle数据库死锁问题的研究与讨论

    数据库死锁-解决死锁问题的三种办法

    解决死锁问题的三种方法:预防死锁,检测死锁及避免死锁。

    Java解决死锁问题eclipse代码版

    Java解决死锁问题eclipse代码版

    山东大学操作系统实验6死锁问题

    山东大学 操作系统实验6 死锁问题实验的程序

    db2死锁问题.doc

    db2死锁问题.doc db2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.docdb2死锁问题.doc

    实验六、 死锁问题实验

    在两个城市南北方向之间存在一条铁路,多列火车可以分别从两个城市的车站 排队等待进入车道向对方城市行驶,该铁路在同一时间,只能允许在同一方向上行 车,如果同时有相向的火车行驶将会撞车。请模拟实现两个方向行车,...

    操作系统-死锁

    从进程同步的概念可以知道,当并发进程需要竞争使用资源或需要相互协作向前推进时,如果... 死锁是所有操作系统都面临着的潜在问题,操作系统除了需要预防死锁、避免死锁外,还需要能够检测死锁,并从死锁中进行恢复。

    Oracle数据库死锁问题研究.pdf

    Oracle数据库死锁问题研究.pdf

    操作系统银行家算法避免进程死锁问题

    安全性算法,银行家算法,避免进程死锁的问题,这是我用C语言编的程序,运行通过。

    哲学家进餐问题死锁的造成.cpp

    死锁的四个条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而...先写一个会造成死锁的哲学家问题。当所有哲学家同时决定进餐,拿起左边筷子时候,就发生了死锁。

    解决Oracle死锁问题.txt

    编译的存储过程的时候,程序死住,等待一会出现ora-04021错误解决办法。文档中有查询思索的语句,以及杀掉死锁进程的方法。

    操作系统中的死锁问题

    有关死锁的介绍。包括死锁的定义,如何处理死锁,死锁如何预防,如何避免等

    银行家问题--避免死锁

    对系统资源的申请和分配,主要是为了避免死锁问题。

    典型死锁问题.rar_操作系统典型死锁问题

    包含了操作系统的三个死锁问题,哲学家问题,消费者生产者问题,管道等,解决方法。绝对可用。

    db2死锁问题分析及解决方案

    db2死锁问题分析及解决方案,可以快速解决数据库问题。

Global site tag (gtag.js) - Google Analytics