Java线程图文总结

jopen 8年前

实现方式

简单介绍一下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:将指定的线程执行完成后,再运行当前线程剩下的任务。典型的例子是将两个交替执行的线程合并顺序执行。请结合下面代码理解:

public static void main(String[] args) throws IOException {   final Thread aThread = new Thread(new Runnable() {    @Override    public void run() {     for (int i = 0; i < 5; i++) {      System.out.println("a:" + i);     }    }   });      Thread bThread = new Thread(new Runnable() {    @Override    public void run() {     try {      aThread.join();     } catch (InterruptedException e) {     }          for (int i = 0; i < 5; i++) {      System.out.println("b:" + i);     }    }   });      bThread.start();   aThread.start();      try {    bThread.join();   } catch (InterruptedException e) {   }      for (int i = 0; i < 5; i++) {    System.out.println("c:" + i);   }  }

输出结果:

a:0  a:1  a:2  a:3  a:4  b:0  b:1  b:2  b:3  b:4  c:0  c:1  c:2  c:3  c:4

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

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


命运三

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

public static void main(String[] args) throws IOException {   final Object obj = 1;   Thread aThread = new Thread(new Runnable() {    @Override    public void run() {     synchronized(obj){      for (int i = 0; i < 3; i++) {       System.out.println("线程a在使用obj...");       try {        Thread.sleep(500);       } catch (InterruptedException e) {       }      }     }    }   });     Thread bThread = new Thread(new Runnable() {    @Override    public void run() {     synchronized(obj){      for (int i = 0; i < 3; i++) {       System.out.println("线程b在使用obj...");       try {        Thread.sleep(500);       } catch (InterruptedException e) {       }      }     }    }   });   bThread.start();   aThread.start();  }

输出结果是:

线程b在使用obj...  线程b在使用obj...  线程b在使用obj...  线程a在使用obj...  线程a在使用obj...  线程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来实现协同工作。请看以下代码:

public static void main(String[] args) {   final Object object = new Object();   Thread a = new Thread() {    public void run() {     System.out.println("a start");     try {      Thread.sleep(2000);     } catch (InterruptedException e) {     }     synchronized (object) {      System.out.println("a finish, notify b and c");      object.notifyAll(); //wait,notify,notifyAll必须在synchronized块里面使用     }    }   };      Thread b = new Thread() {    public void run() {     synchronized (object) {      System.out.println("b is starting, waiting for a finish...");      try {       object.wait();      } catch (InterruptedException e) {      }      System.out.println("b end");     }    }   };      Thread c = new Thread() {    public void run() {     synchronized (object) {      System.out.println("c is starting, waiting for a finish...");      try {       object.wait();      } catch (InterruptedException e) {      }      System.out.println("c end");     }    }   };     a.start();   b.start();   c.start();  }

其中一次的输出结果:

a start  b is starting, waiting for a finish...  c is starting, waiting for a finish...  a finish, notify b and c  c end  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