Java 并发学习系列(一)

Java并发系列学习(一)

众所周知, Java并发系列编程一直都是Java程序员难以轻易绕过的山,可谓之小高之山也。Java生态圈中提供了非常丰富的并发编程类库,但是这样子也造就了非常多的人知其然而不知其所以然,很多人只会用,却不知其底层的运行机制,不知其优势与缺陷,也就无法将其融会贯通,做到信手拈来。何况,即便非常完善的类库也无法满足所有的业务需求,适当的时候我们可能要自己编写类库来支撑自己的业务。在这个系列中,我们一起来深入学习并发编程,一起成长。本人能力有限,若有错误,请及时指正。

线程状态

想要学习好多线程编程,无疑要先明白,线程在整个运行期间的状态,又是通过何种机制,或者说何种方式让线程在这些状态之间切换?

如上图,大致可以说明Java线程的生命周期。

新建状态

在多线程编程中,我们通常会使用 ThreadRunnable关键字来创建线程,如下:

 
  1. Thread thread = new Thread(new Runnable(){

  2.   @Override

  3.   public void run() {

  4.     System.out.println("thread");

  5.   }            

  6. });

当这个线程被创建出来之后,如果你没有调用 start()(或者其他方式比如当如线程池中调用 submit()),它与普通对象并无区别(有面试官可能会在这里考你,至少我经历过)。此时OS调度器不会为它分配CPU执行需要的时间分片。

就绪状态

当线程被创建出来之后,它必须进入就绪状态,才可能被分配CPU时间分片,才可以可能被操作系统调度。通常情况下,我们会通过调用 start(),或者线程池的 submit()让线程进入就绪状态,如下:

 
  1. thread.start(); // 普通线程

  2. ExecutorService executor = Executors.newSingleThreadExecutor();// 创建线程池并将线程提交给线程池管理

  3. executor.submit(thread);

线程进入就绪状态大概有如下几种方式:

  • 线程从新建状态进入就绪状态,比如(start(),executor.submit())

  • 线程从运行状态恢复到就绪状态,(比如正在执行的线程被挂起,CPU时间分片用完,yield())

  • 线程从阻塞状态恢复到就绪状态(比如sleep执行完毕,join执行完毕)

  • 在锁池中等待的锁拿到了锁的权限

运行状态

当线程处于就绪状态时,随时有可能被操作系统调度。线程从就绪状态到被操作系统调度称为线程进入运行状态,语义上是: run()方法正在接受操作系统调度,正在被执行。

阻塞状态

当线程从运行状态调用了 sleep()或者 join()等方法的时候,会进入阻塞状态,等待执行时间到,或者调用 join()的线程执行完毕,线程会自动进入就绪状态,如下:

 
  1. try {

  2.  thread.sleep(1000);

  3. } catch (InterruptedException e) {

  4.  e.printStackTrace();

  5. }

  6. try {

  7.  thread.join();

  8. } catch (InterruptedException e) {

  9.  e.printStackTrace();

  10. }

终止/死亡状态

当线程执行 run()方法完毕,或者因为发生异常而被中断,或者 interrupt()(另,在jdk中依旧可以看到一些已经被废弃的诸如 stop(),supend()等等),线程会进入死亡状态。如果线程执行完 run()方法之后,线程进入死亡状态,并不意味该线程的对象被回收,线程也可以再次被唤醒。

正在运行的线程进入等待队列

正在运行的线程进入等待队列

当正在运行线程调用 wait()的时候,线程会丢失CPU时间分片,进入一个等待状态,等待其他线程调用 notify()/notifyAll()的时候,将其唤醒。如果没有被唤醒,线程一直处于等待状态,也可以称之为假死状态。注意区别好线程调用 sleep()进入睡眠状态,二者之间不尽相同,调用 sleep()的语义是线程需要进入休眠状态,休眠时间是在已经被设定好的,时间到了之后会自行进入就绪状态等待操作系统调度。线程调用 wait()的目的是拥有当前锁对象的线程不需要继续执行(释放锁,让出CPU时间分片,且不会立刻进入就绪状态),进入等待队列的线程需要被 notify()/notifyAll()唤醒。唤醒后,线程会进入锁池中排队去等待,直到拿到锁的权限,只有拿到锁的权限才可以进入就绪状态等待操作系统调度。

当我们在调用 wait(), notify(), notifyAll()方法的时候,编译时可以正常通过,但是运行期间可能会抛出异常 IllegalMonitorStateException,这是为何?在调用 wait()语句的时候,前置条件是:当前线程必须拥有当前对象的锁的权限。通常情况下,我们通常会说在调用 wait()方法之前,必须先获得 synchronized的对象锁【这个语句本身不对,但是可以很好说明问题】。

比如调用了线程的 join()方法的时候,如果细心的同学会去查看源码,会发现在其内部是通过调用 wait(0)方法来达到让当前正在执行的线程进入等待状态,源码如下:

 
  1. public final synchronized void join(long millis)

  2.    throws InterruptedException {

  3.        // ....

  4.        if (millis == 0) {

  5.            while (isAlive()) {

  6.                wait(0);// 可以考虑wait()与wait(timeout)的区别

  7.            }

  8.        }

  9.        // ....

  10.    }

进入锁池

当正在执行的线程遇到 synchronized时,或者在等待队列中的线程被 notify()notifyAll()唤醒的线程,会进入锁池中,直到拿到锁之后会进入就绪状态继续执行。 synchronized关键字自不必多说,线程同步原语,由内置语言实现。当一个线程已经进入这个锁,并且还未完成释放,其他的线程执行到这里,想要继续执行就需要进入锁池中排队等待当前的锁释放,直到获取锁(不一定是先到先得)才可以进入就绪状态。 前边提到,调用 wait()方法需要先获得锁,它的语义更多的可能(猜测)是:当线程获取锁之后,在执行过程中,由于某些条件不满足而选择主动进入等待状态,直到其他线程处理完一些事项后它才会被唤醒并被操作系统重新调度。所以,只有在等待队列中的线程才可以调用 notify()notifyAll()来唤醒。

以上为笔者对于线程的生命周期的理解,如若有错误,请大侠指正!

                       

猜你喜欢

转载自my.oschina.net/u/1589819/blog/1645065