在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

Java线程图文总结

2017-2-7 13:38| 发布者: zhangjf| 查看: 520| 评论: 0

摘要: 实现方式 简单介绍一下Java多线程实现方式,有以下三种: 1、继承Thread类 2、实现Runnable接口 3、使用ExecutorService、Callable、Future实现有返回结果的多线程 区别是前两种执行完之后不带返回值,最后一 ...

实现方式

简单介绍一下Java多线程实现方式,有以下三种:

1、继承Thread类

2、实现Runnable接口

3、使用ExecutorService、Callable、Future实现有返回结果的多线程

区别是前两种执行完之后不带返回值,最后一种带返回值,其中最常用为前两种。


线程的状态

java线程的整个生命周期有5个状态:新建,就绪,运行中,阻塞,结束。

5个状态之间的关系将结合下图理解:

上图为java线程生命周期期间的各种命运,下面介绍常见的几种命运。

命运一

新线程创建成功,调用start()进入就绪状态,即进入待运行的线程池中等待,等待获取CPU的使用权。当获得CPU使用权,该线程从就绪状态进入运行状态。运行过程中,运气好的,一次运行就把所要执行的任务执行完毕,线程结束;命运不好的,运行中途被CPU暂停运行,重新回到就绪状态,等待分配,然后再等待进入运行期,直到最后运行完毕,最后结束。


命运二

新线程创建成功,进入就绪状态,获取了CPU使用权,处于运行状态。这里意外出现,该线程执行了sleep、yield、join三者其中一个命令。sleep、join需要被暂停执行一段时间,线程进入阻塞状态。休息时间到,再重新进入就绪状态;而yield是从运行状态直接跳会就绪状态。当到了就绪状态后再重新等待CPU调度,重新进入运行期。run -> block -> run -> block.... 此种状态会持续,一直到该线程的任务执行完毕。sleep、yield、join三者区别如下:

sleep:CPU暂停当前线程运行,同时让就绪状态(待运行池中)优先级较低的一个线程运行。当前线程被暂停后,会处于阻塞状态n秒(n由开发人员设定),时间到了之后,会自动回到就绪状态,等待CPU重新调度,重新从刚刚暂停的地方运行。注意,若代码块中包含了对象的锁,在睡眠的过程中是不会释放掉对象的锁的,其他线程是不能访问到共享数据的。

join:将指定的线程执行完成后,再运行当前线程剩下的任务。典型的例子是将两个交替执行的线程合并顺序执行。请结合下面代码理解:

  1. public static void main(String[] args) throws IOException {
  2. final Thread aThread = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 5; i++) {
  6. System.out.println("a:" + i);
  7. }
  8. }
  9. });
  10. Thread bThread = new Thread(new Runnable() {
  11. @Override
  12. public void run() {
  13. try {
  14. aThread.join();
  15. } catch (InterruptedException e) {
  16. }
  17. for (int i = 0; i < 5; i++) {
  18. System.out.println("b:" + i);
  19. }
  20. }
  21. });
  22. bThread.start();
  23. aThread.start();
  24. try {
  25. bThread.join();
  26. } catch (InterruptedException e) {
  27. }
  28. for (int i = 0; i < 5; i++) {
  29. System.out.println("c:" + i);
  30. }
  31. }
复制代码

输出结果:

  1. a:0
  2. a:1
  3. a:2
  4. a:3
  5. a:4
  6. b:0
  7. b:1
  8. b:2
  9. b:3
  10. b:4
  11. c:0
  12. c:1
  13. c:2
  14. c:3
  15. c:4
复制代码

如果把代码中的join部分去掉,就不能保证a、b、c的输出顺序。

yield:实质是当前正在运行的线程直接回到就绪状态,而不用进入阻塞状态等待,且只会选择优先级相同的线程进入运行状态。若待运行的线程池中,没有跟当前线程优先级相同的线程,或者当前线程又被选中运行,则当前线程还是会继续运行,会误造成yield无法达到目的的效果。


命运三

线程a成功进入创建、就绪、运行流程,运行过程中,需要访问资源i,而此时资源i正在被另外的线程b访问并且上锁了,此时线程a就会暂停运行,进入资源i的锁池,等待线程b释放资源i的锁。当线程a获得资源i的锁时,会从资源i的锁池中进入就绪状态(待运行池),等待调度。以下为示例代码:

  1. public static void main(String[] args) throws IOException {
  2. final Object obj = 1;
  3. Thread aThread = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. synchronized(obj){
  7. for (int i = 0; i < 3; i++) {
  8. System.out.println("线程a在使用obj...");
  9. try {
  10. Thread.sleep(500);
  11. } catch (InterruptedException e) {
  12. }
  13. }
  14. }
  15. }
  16. });
  17. Thread bThread = new Thread(new Runnable() {
  18. @Override
  19. public void run() {
  20. synchronized(obj){
  21. for (int i = 0; i < 3; i++) {
  22. System.out.println("线程b在使用obj...");
  23. try {
  24. Thread.sleep(500);
  25. } catch (InterruptedException e) {
  26. }
  27. }
  28. }
  29. }
  30. });
  31. bThread.start();
  32. aThread.start();
  33. }
复制代码

输出结果是:

  1. 线程b在使用obj...
  2. 线程b在使用obj...
  3. 线程b在使用obj...
  4. 线程a在使用obj...
  5. 线程a在使用obj...
  6. 线程a在使用obj...
复制代码

两个线程都start()之后,两个线程随机先后访问到obj对象,有可能是a先访问obj,有可能是b先访问obj。我的结果就是b先访问obj,拿到obj的锁,之后线程a无法立即访问到obj,a就进入obj的锁池中等待;当b中被锁的代码块跑完且释放锁,a拿到obj的锁,重新进入就绪状态,等待分配运行。


命运四

线程a成功的创建、就绪、运行,运行过程中调用obj.wait(),a就从run状态进入到obj的等待池,等待其他线程调用obj.notify;当其他线程调用notify之后,线程a从obj的等待池中,进入到obj的锁池,等待获得锁以重新进入就绪状态恢复运行。下面具体解析一下wait、notify、notifyAll的作用:

wait:在已经获得obj的锁的前提下,主动释放obj的锁,释放后当前线程会进入obj的等待池,即开始休眠。另外,当调用了wait之后,并不是立即释放对象obj的锁,而是在相应的synchronized()语句块执行结束后才真正释放。

notify的作用是从对象obj的等待池中随机抽取一个线程放到对象obj的锁池中,让该线程继续在锁池中等待锁,成功拿到锁后,等待分配执行。

notifyAll的作用跟notify类似,只不过是把对象obj的等待池中的所有的线程全部取出,放到对象obj的锁池中。

需要明确的一点:wait、notify、notifyAll均为在当前线程获取对象的锁的前提下执行的,所以这三个操作都必须在synchronized()块中执行。

举个例子:线程a、线程b、线程c需要协同工作,线程b、c的任务需要在a线程的任务完成后才能开始。那就是说a在完成任务后,需要通知b和c恢复工作。这种情况就可通过wait,notifyAll来实现协同工作。请看以下代码:

  1. public static void main(String[] args) {
  2. final Object object = new Object();
  3. Thread a = new Thread() {
  4. public void run() {
  5. System.out.println("a start");
  6. try {
  7. Thread.sleep(2000);
  8. } catch (InterruptedException e) {
  9. }
  10. synchronized (object) {
  11. System.out.println("a finish, notify b and c");
  12. object.notifyAll(); //wait,notify,notifyAll必须在synchronized块里面使用
  13. }
  14. }
  15. };
  16. Thread b = new Thread() {
  17. public void run() {
  18. synchronized (object) {
  19. System.out.println("b is starting, waiting for a finish...");
  20. try {
  21. object.wait();
  22. } catch (InterruptedException e) {
  23. }
  24. System.out.println("b end");
  25. }
  26. }
  27. };
  28. Thread c = new Thread() {
  29. public void run() {
  30. synchronized (object) {
  31. System.out.println("c is starting, waiting for a finish...");
  32. try {
  33. object.wait();
  34. } catch (InterruptedException e) {
  35. }
  36. System.out.println("c end");
  37. }
  38. }
  39. };
  40. a.start();
  41. b.start();
  42. c.start();
  43. }
复制代码

其中一次的输出结果:

  1. a start
  2. b is starting, waiting for a finish...
  3. c is starting, waiting for a finish...
  4. a finish, notify b and c
  5. c end
  6. b end
复制代码

因为线程在锁池中是被随机抽取的,所以不可能保证b,c哪个先运行。上面的结果就是c先被抽取。

上述全部讨论的前提是线程运行中没有遇到exception的情况,若遇上了exception,就直接end。


以上为本人对Java线程的理解,是基于线程的浅层部分展开讨论,欢迎指正。若想深入了解线程,可以看看java.util.concurrent包下的类。本人之前写过一个基于多线程实现的远程监控程序,有兴趣可参考一下:

http://my.oschina.net/ericquan8/blog/383882



来自: http://my.oschina.net/ericquan8/blog/384655

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-8 20:03

Copyright 2015-2025 djqfx

返回顶部