这两天为了定位JBOSS老是挂死的问题,学习了一下JAVA多线程方面的知识,在此总结一下

1、在Java程序中,JVM负责线程的调度。线程调度是指按照特定的机制为多个线程分配CPU的使用权。 
调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。

2、Thread类实际上也是实现了Runnable接口的类。 
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。 
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

3、JAVA多线程涉及到2个问题,一个是线程的调度,另一个是线程的同步

4、线程的状态有:new、runnable、running、waiting、timed_waiting、blocked、dead

当执行new Thread(Runnable r)后,新创建出来的线程处于new状态,这种线程不可能执行

当执行thread.start()后,线程处于runnable状态,这种情况下只要得到CPU,就可以开始执行了。runnable状态的线程,会接受JVM的调度,进入running状态,但是具体何时会进入这个状态,是随机不可知的

running状态中的线程最为复杂,可能会进入runnable、waiting、timed_waiting、blocked、dead状态: 
如果CPU调度给了别的线程,或者执行了Thread.yield()方法,则进入runnable状态,但是也有可能立刻又进入running状态 
如果执行了Thread.sleep(long),或者thread.join(long),或者在锁对象上调用object.wait(long)方法,则会进入timed_waiting状态 
如果执行了thread.join(),或者在锁对象上调用了object.wait()方法,则会进入waiting状态 
如果进入了同步方法或者同步代码块,没有获取锁对象的话,则会进入blocked状态

处于waiting状态中的线程,如果是因为thread.join()方法进入等待的话,在目标thread执行完毕之后,会回到runnable状态;如果是因为object.wait()方法进入等待的话,在锁对象执行object.notify()或者object.notifyAll()之后会回到runnable状态

处于timed_waiting状态中的线程,和waiting状态中的差不多,只不过是设定时间到了,就会回到runnable状态

处于blocked状态中的线程,只有获取了锁之后,才会脱离阻塞状态

当线程执行完毕,或者抛出了未捕获的异常之后,会进入dead状态,该线程结束

5、当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。

6、设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以更改线程的优先级。

JVM从不会改变一个线程的优先级。然而,1-10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。

7、Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

8、另一个问题是线程的同步,这个我感觉比调度更加复杂一些

Java中每个对象都有一个“内置锁”,也有一个内置的“线程表”

当程序运行到非静态的synchronized方法上时,会获得与正在执行代码类的当前实例(this实例)有关的锁;当运行到同步代码块时,获得与声明的对象有关的锁

释放锁是指持锁线程退出了synchronized方法或代码块。

当程序运行到synchronized同步方法或代码块时对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

9、当提到同步(锁定)时,应该清楚是在哪个对象上同步(锁定)?

10、

obj.wait() 
obj.notify() 
obj.notifyAll()

关于这3个方法,有一个关键问题是:

必须从同步环境内调用wait()、notify()、notifyAll()方法。只有拥有该对象的锁的线程,才能调用该对象上的wait()、notify()、notifyAll()方法

与每个对象具有锁一样,每个对象也可以有一个线程列表,他们等待来自该对象的通知。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。

11、下面贴几个代码实例,配合jstack命令说明一下

  1. public class ThreadA {
  2. public static void main(String[] args) {
  3. ThreadB b = new ThreadB();// ThreadB status: new
  4. b.start();// ThreadB status: runnable
  5. synchronized (b) {
  6. try {
  7. System.out.println("等待对象b完成计算。。。");
  8. Thread.sleep(60000);
  9. b.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("b对象计算的总和是:" + b.total);
  14. }
  15. }
  16. }
  17. public class ThreadB extends Thread {
  18. int total;
  19. public void run() {
  20. synchronized (this) {
  21. for (int i = 0; i < 101; i++) {
  22. total += i;
  23. }
  24. notifyAll();
  25. }
  26. }
  27. }

jstack输出的结果是:

"main" prio=6 tid=0x00846800 nid=0x1638 waiting on condition [0x0092f000] 
   java.lang.Thread.State: TIMED_WAITING (sleeping) 
at java.lang.Thread.sleep(Native Method) 
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20) 
- locked <0x22a18a90> (a net.kyfxbl.lock.ThreadB)

"Thread-0" prio=6 tid=0x02bbb800 nid=0x1410 waiting for monitor entry [0x02f0f000] 
   java.lang.Thread.State: BLOCKED (on object monitor) 
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:11) 
- waiting to lock <0x22a18a90> (a net.kyfxbl.lock.ThreadB)

可以看到,主线程和新线程在同一个对象上锁定,主线程的方法里执行了Thread.sleep(60000),因此进入了TIMED_WAITING状态,而新线程则进入BLOCKED状态

  1. public class ThreadA {
  2. public static void main(String[] args) {
  3. ThreadB b = new ThreadB();// ThreadB status: new
  4. b.start();// ThreadB status: runnable
  5. synchronized (b) {
  6. try {
  7. System.out.println("等待对象b完成计算。。。");
  8. b.wait();
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("b对象计算的总和是:" + b.total);
  13. }
  14. }
  15. }
  16. public class ThreadB extends Thread {
  17. int total;
  18. public void run() {
  19. synchronized (this) {
  20. try {
  21. Thread.sleep(60000);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. for (int i = 0; i < 101; i++) {
  26. total += i;
  27. }
  28. notifyAll();
  29. }
  30. }
  31. }

jstack输出的结果是:

"main" prio=6 tid=0x00846800 nid=0x1684 in Object.wait() [0x0092f000] 
   java.lang.Thread.State: WAITING (on object monitor) 
at java.lang.Object.wait(Native Method) 
- waiting on <0x22a18b08> (a net.kyfxbl.lock.ThreadB) 
at java.lang.Object.wait(Object.java:485) 
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:22) 
- locked <0x22a18b08> (a net.kyfxbl.lock.ThreadB)

"Thread-0" prio=6 tid=0x02bcc800 nid=0x19c waiting on condition [0x02f0f000] 
   java.lang.Thread.State: TIMED_WAITING (sleeping) 
at java.lang.Thread.sleep(Native Method) 
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:12) 
- locked <0x22a18b08> (a net.kyfxbl.lock.ThreadB)

2个线程还是在同一个对象上同步,但这次主线程立刻执行了b.wait()方法,因此释放了对象b上的锁,自己进入了WAITING状态。接下来新线程得到了对象b上的锁,所以没有进入阻塞状态,紧接着执行Thread.sleep(60000)方法,进入了TIMED_WAITING状态

  1. public class ThreadA {
  2. public static void main(String[] args) {
  3. ThreadB b = new ThreadB();// ThreadB status: new
  4. b.start();// ThreadB status: runnable
  5. synchronized (b) {
  6. try {
  7. System.out.println("等待对象b完成计算。。。");
  8. b.wait();// ThreadB status: running
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("b对象计算的总和是:" + b.total);
  13. }
  14. }
  15. }
  16. public class ThreadB extends Thread {
  17. int total;
  18. public void run() {
  19. synchronized (this) {
  20. for (int i = 0; i < 101; i++) {
  21. total += i;
  22. }
  23. notifyAll();
  24. try {
  25. System.out.println("我要睡了");
  26. Thread.sleep(60000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }

jstack输出的结果是:

"main" prio=6 tid=0x00846800 nid=0x3ec in Object.wait() [0x0092f000] 
   java.lang.Thread.State: BLOCKED (on object monitor) 
at java.lang.Object.wait(Native Method) 
- waiting on <0x22a18ba0> (a net.kyfxbl.lock.ThreadB) 
at java.lang.Object.wait(Object.java:485) 
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20) 
- locked <0x22a18ba0> (a net.kyfxbl.lock.ThreadB)

"Thread-0" prio=6 tid=0x02bbb800 nid=0x14b4 waiting on condition [0x02f0f000] 
   java.lang.Thread.State: TIMED_WAITING (sleeping) 
at java.lang.Thread.sleep(Native Method) 
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:19) 
- locked <0x22a18ba0> (a net.kyfxbl.lock.ThreadB)

当主线程执行b.wait()之后,就进入了WAITING状态,但是新线程执行notifyAll()之后,有一个瞬间主线程回到了RUNNABLE状态,但是好景不长,由于这个时候新线程还没有释放锁,所以主线程立刻进入了BLOCKED状态

12、当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程仍然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁被释放

13、与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。

14、在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。

15、JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台线程时候一定要注意这个问题。

16、下面说说我们这次JBOSS挂死问题的处理方法

现象:系统运行一段时间之后,发现有几个子系统无法访问了,但是另外几个可以。CPU占用达到100%

观察了一下,发现无法访问的应用都部署在同一个JBOSS里,于是把该JBOSS的堆栈用jstack命令输出

发现里面有大量的线程处于BLOCKED状态,均是在执行到c3p0的一个方法里的某一行时,BLOCKED住了

于是下载c3p0的源码,跟进去看了一下,这是一个同步方法,内部会去获取数据库连接,如果获取到连接,就进行下一步操作,如果获取不到,就执行sleep(long timeout)方法。

反推一下,我猜测可能是这样的:

由于某段代码没有释放数据库连接-->连接池中的连接耗尽-->部分线程无限TIMED_WAITING-->其余线程都BLOCKED-->开启新线程-->频繁引发GC-->占用大量CPU-->应用挂起

后来对所有涉及到数据库连接的代码进行排查,发现确实有几个地方做完数据库操作以后,没有释放连接。把这部分代码改掉,重新启动JBOSS,没有再出现JBOSS挂起的现象

java多线程解决应用挂死的问题的更多相关文章

  1. java多线程解决生产者消费者问题

    import java.util.ArrayList; import java.util.List; /** * Created by ccc on 16-4-27. */ public class ...

  2. Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型

    http://blog.csdn.net/a352193394/article/details/39503857  Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...

  3. Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例

    在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...

  4. JAVA多线程之线程的挂起与恢复(suspend方法与resume方法)

    一,介绍 本文讨论JAVA多线程中,使用 thread.suspend()方法暂停线程,使用 thread.resume()恢复暂停的线程 的特点. 先介绍二个关于线程的基本知识: ①线程的执行体是r ...

  5. Java多线程之生产者消费者问题&lt;一&gt;:使用synchronized keyword解决生产者消费者问题

    今天看了一片博文,讲Java多线程之线程的协作,当中作者用程序实例说明了生产者和消费者问题,但我及其它读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的样例也都存在bug,经过细致研究发现当中的问 ...

  6. 死磕java多线程

    1.线程和进程 1.1线程和进程的区别 进程 它是内存中的一段独立的空间,可以负责当前应用程序的运行.当前这个进程负责调度当前程序中的所有运行细节(操作系统为进程分配一块独立的运行空间): 线程 它是 ...

  7. Java多线程开发系列之一:走进多线程

    对编程语言的基础知识:分支.选择.循环.面向对象等基本概念理解后,我们需要对java高级编程有一定的学习,这里不可避免的要接触到多线程开发. 由于多线程开发整体的系统比较大,我会写一个系列的文章总结介 ...

  8. 从JAVA多线程理解到集群分布式和网络设计的浅析

    对于JAVA多线程的应用非常广泛,现在的系统没有多线程几乎什么也做不了,很多时候我们在何种场合如何应用多线程成为一种首先需要选择的问题,另外关于java多线程的知识也是非常的多,本文中先介绍和说明一些 ...

  9. ***Java多线程发展简史

    http://blog.jobbole.com/28297/ 本文来自四火的博客(@RayChase),由@_Zhijun 推荐 这篇文章,大部分内容,是周五我做的一个关于如何进行Java多线程编程的 ...

随机推荐

  1. empireCMS 帝国cms功能总结

    上1 系统 对应左菜单为 系统设置 系统参数设置 基本属性 站点名称,网站地址,关键字,简介,首页模式,php时间, 前台功能,操作时间,来源地址,验证码 用户属性 后台验证码开启,次数限制,时间限制 ...

  2. Tasker to answer incoming call by pressing power button

    nowadays, the smartphone is getting bigger in size, eg. samsung galaxy note and note 2, sorta big in ...

  3. JAVA设计模式之门面模式(外观模式)

    医院的例子 现代的软件系统都是比较复杂的,设计师处理复杂系统的一个常见方法便是将其“分而治之”,把一个系统划分为几个较小的子系统.如果把医院作为一个子系统,按照部门职能,这个系统可以划分为挂号.门诊. ...

  4. 盘点Linux内核源码中使用宏定义的若干技巧(1)

    http://blog.chinaunix.net/uid-23769728-id-3141515.html

  5. [Asp.net web api]缓存

    摘要 为了提高接口的性能,我们常做的优化就包括缓存,对经常访问但变化不大的数据进行缓存.或者使用http的缓存,减少请求的次数. web api缓存 在提供的api,我们也可以实现缓存,来减少访问的次 ...

  6. Moq的一些基本用法

    本篇体验Moq的一些基本用法.首先通过NuGet安装Moq.包括: 模拟方法的返回值 模拟方法后执行回调函数 模拟方法依次返回多个值 模拟第二次调用方法返回异常 直接返回被模拟方法的原始返回值 模拟泛 ...

  7. [原] corePlot 类库与iOS自带类库使用方法对比(很多开源代码都有这个特点)

    ——人类最倚重的是自己的“以往经验”.—— 我们直接看一下在corePlot 类库和iOS自带类中为一个控件设置文本显示格式的实现.   * corePlot 类库中,为一个对象设置标题显示格式 , ...

  8. arcgis10.5新功能图形缓冲

    摘要 在输入要素周围某一指定距离内创建缓冲区多边形.在要素周围生成缓冲区时,大部分制图形状对缓冲区末端(端头)和拐角(连接)可用. 插图  

  9. 聊聊高并发(十四)理解Java中的管程,条件队列,Condition以及实现一个堵塞队列

    这篇里面有一些主要的概念,理解概念是件有意义的事情,仅仅有理解概念才干在面对详细问题的时候找到正确的解决思路.先看一下管程的概念 第一次在书上看到管程这个中文名称认为非常迷糊,管程究竟是个什么东东,于 ...

  10. 每天进步一点点——论fork()函数与Linux中的多线程编程

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/27316803 一.fork()函数     在操作系统的基本概念中进程是程序的一次运行,且是 ...