Java线程之核心概念

1.线程的三个基本角色

线程是一个执行流程,它不是类,也不是对象。我们先来梳理一下线程的三个基本角色:

  1. Runnable接口:任务入口角色。Runnable的run方法跟主线程的main方法充当的角色一致;
  2. Thread类:线程管理角色。可以设置/获取this thread的属性,管理this thread的生命周期和监控干预current thread等功能;
  3. 锁:同步和协作角色。每个对象(Object)都是一把锁。结合synchronized原语和锁(Object)中的wait()/wait(long timeout)/    wait(long timeout, int nanos)/notify()/notifyAll()方法,起到协调多线程的作用。

Runnable和Thread不是一个概念的东西,一个是任务入口,一个是线程管理员。创建一个线程最佳的办法应该是先创建一个实现了Runnable接口的类,然后再把它注入Thread对象,而不是直接继承Thread对象。这样程序才看起来职责分明。

this thread是指由某个Thread对象管理的那个线程。current thread是指运行时的那个线程。我们平时可能很少注意这两者的区别,经常弄不清被操作者是this thread还是current thread(操作者总是current thread)。我这里做个总结:

  • 锁操作的对象永远是current thread;
  • Thread的静态方法操作的对象永远是current thread;
  • Thread的成员方法操作的对象永远是this thread;

举一个很经典的例子:interrupt()是一个成员方法,会让this thread中断;interrupted()是一个静态方法,作用是检测current thread是否是中断的;    isInterrupted()又是一个成员方法,作用是检测this thread是否是中断的。

当然,this thread可以转化为current thread。通过Thread类的静态方法currentThread(),就可以得到运行时线程的Thread对象,这个Thread对象调用成员方法的时候,操作的也是current Thread,因为这时this thread和current Thread是同一个线程。Thread.interrupted()和Thread.currentThread().isInterrupted()是等效的。

任何对象都可以是锁。锁的作用有两个:同步和协作。wait()/wait(long timeout)/    wait(long timeout, int nanos)/notify()/notifyAll()都是锁的方法,如果一个对象没有被当做锁,调用这些方法就会报错。synchronized可以让一个对象成为一把锁,起到同步的作用,而锁的这些方法可以协调需要这把锁的多个线程。

好了,现在梳理清楚了线程的三个基本角色,以及它们之间的关系。接下来总结一下多线程的两大用处.

2.线程的两大用处

多线程其实就两个用处,一是并行,二是并发。并行和并发的区别可以看下图。并行带来的好处显而易见,多一个咖啡机,我们就能早点喝到咖啡。并行就不一定了,排了两个队,只有一个咖啡机,在拿咖啡的时候两队最前面的两个人还要抢一下(线程同步开销),才能喝到咖啡,我们可能会因此更晚喝到咖啡。

但是并发其实好处也很多,比如除了消费者,还有咖啡供应者,假设每个咖啡机消费者一分钟能消费一杯咖啡,供应者十分钟才能做好一杯新鲜的咖啡放在咖啡机。那怎么办呢?那就每个咖啡机要十个以上供应者(算上同步开销的话),才能满足消费者的需求。

并行是多个线程在各自状态环境下运行。并发是多个线程在同一个状态环境下运行。并发通常需要引入同步机制,维护状态的一致性。

选择并行和并发,关键看线程之间需不需要共享本地内存数据,如果不需要,就选择并行。如果需要,就选择并发,并且想好同步机制。

 

3.线程的同步和协作细节(并发)

然后再看一下,并发线程之间是怎么同步和协作的,直接上图(这是《图解Java多线程模式》书中的图,图美盗之):

 

 

 

注意:

  1. 图中的等待队列只是一个虚拟模型,并不是锁对象里面真实的数据结构。
  2. 锁同步的对象不是行为(方法),而是行为访问的状态数据(成员变量)。如果要维护一个状态数据的一致性,就要把所有会访问它的方法同步起来。被同一把锁保护起来的多个方法,被看做一个原子操作。
  3. Java里面的对象包含三要素:唯一标识,状态和行为。三者总是绑定在一起的。如果Java对象的状态是不可变的(无状态、final),就没必要同步。

 

4.线程的状态

最后,总结一下线程的生命周期:

线程的状态包括:初始状态、可执行状态、执行状态、等待状态、阻塞状态、终止状态。等待状态和阻塞状态有什么区别呢?阻塞是为了同步(和敌人在一起的时候),等待是为了协作(和朋友在一起的时候)。这两种状态都不占用cpu,执行状态才占用cpu。

 

5.并发包都有什么

从Java 5开始,引入了并发包。在后续的文章会逐一提到里面的详细内容。这里只是提一下,Java并发包里面其实没有新东西,就是让Java多线程代码实现起来更简单些,里面所有的东西都可以自己借助本文提到的三大角色实现一遍。

猜你喜欢

转载自my.oschina.net/leaforbook/blog/1823980