分类: Java
2010-01-27 15:16:22
@ http://zcatt.cublog.cn
Hist
20060912, draft
20100127, update for cublog
20100722, add ThreadLocal etc.
Thread的基本问题包括,并行/并发Thread的创建和结束,同步互斥机制,和调度机制。
Thread的创建很简单,有关的接口是java.lang.Runnable。
同步互斥机制是指解决不同线程之间的同步和资源竞争问题的方法体系。下文中有详述。
java.lang.Thread中虽然有setPriority()/getPriority()方法,但程序员不应当对java的调度机制有过多的期望,实际上这部分是依赖于VM在不同OS上的实现的。通常的问题是想当然认为java是基于优先级的分时调度,这是不对的。JDK5 tutorial中确认sun java是基于优先级的,但不保证分时(timeslicing)。简单讲,设计中最好依赖于同步互斥而不要基于vm的调度。
代码如下:
//implement your own runnable class
class MyRunnable implements Runnable
{
public void run()
{
......
}
}
//create your thread obj
MyRunnable r = new MyRunnable(...);
Thread t = new Thread(r);
//run your thread
t.start();
创建thread很简单,首先定义自己的Runnable类,然后创建Thread,调用start方法就可以启动thread了,thread将执行Runnable中的run()方法。
不推荐直接继承Thread类的编程方法。
Thread的状态如下图。
new state
|
| start
|
V
sleep,wait, block on I/O, wait for lock
runnable state<--------------------------------------------------------> blocked state
done sleep,notify, IO complete, lock available
|
| stop / run() exist
|
V
dead state
Thread有4个状态,new, runnable, blocked,和dead。有一点,runnable状态下的thread在java的调度下既可以正在执行也可以是在等待执行,runnable表明thread具有了被执行的资格,等待被调度执行。
java.lang.Thread.State定义了6个状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。
每个Oject类的实例拥有一个自己的lock(jdk中称作monitor),一般情况下这个用作同步互斥的object就指的是Thread自己,因为Thread类的父类就是Object类。lock就是实现thread同步互斥的核心。Object类中有3个基本的方法:wait(),notify(), 和notifyAll()。
当Thread拥有某个object[*]的lock时,它可以调用这个object的wait(),这样它就让出lock的所有权(ownership)而进入blocked state。直到其他thread调用这个object的notify()或notifyAll()方法唤醒它,并且同时它可以重新得到lock的所有权的时候,它才重新进入runnable state。如果其他thread调用了notify()/notifyAll(),而它无法重新得到lock的所有权,那么它仍然无法进入runnable state。
只有当thread拥有object的lock时,调用上面3个方法才有意义。那么什么叫“thread拥有了object的lock”呢? JDK中规定了在下述3种境况下就是“thread拥有了object的lock”,
1)thread正在执行用synchronized定义的object的实例方法(不是类方法)。
2)thread正在执行用synchronized(object)定义的代码段。
3)当thread正在执行类的synchronized静态方法时,认为thread拥有这个类对应的java.lang.Class实例的lock。
lock有两个基本的特性。第一个特性是一个时刻只能被一个thread拥有,也就是不可能有两个或两个以上的thread同时拥有同一个lock。这个性质保证了可以凭借lock实现thread间同步互斥。第二个特性是可重入性(reentrant),可重入性是指如果thread拥有某个lock,那么thread可以调用这个lock的synchronized方法而不被阻塞,这就保证了thread不会被已经拥有的lock阻塞掉。
Java中有一类特殊的thread,就是daemon thread。运行java应用程序,当程序中只有daemon thread时,java应用程序将退出。
Thread.setDaemon(boolean on)可以将Thread设置为Daemon Thread。
当thread处于blocked state时,如何告知thread诸如“应当退出了”等消息。interrupt机制可以实现这个任务。
Thread对象内部有一个布尔状态量interrupt status(interrupt状态),当调用interrupt()方法时会影响到这个布尔量。基本算法是这样的,
1)如果thread在runnable state,那么调用interrupt()会设置interrupt status为true。
2)如果thread在blocked state()[**],那么调用interrupt()会清除interrupt status为false,并且触发thread的InterruptException。([**]注,严格讲应当是阻塞在wait(),join(),和sleep()上。详细查JDK。)
利用2),blocked state的thread可以及时处理各种紧急消息,通常是退出请求。
如果允许,请不要设计自私(selfish)的thread,给其他的thread执行的机会,尤其在infinite loop情况下。自私的thread策略从根本上与使用多thread的初衷抵触。
通常有两种实现避免selfish。
1)调用yield();
2)调用sleep(0);
两种方法的差异研究中。目前看,yield()更依赖于vm的调度策略,另外yield只会给同优先级的thread机会,不会给低优先级thread机会。在有同优先级thread存在的情况下,yield()肯定会使别的thread得到运行,而依赖于优先级调度,sleep(0)似乎就不见的(有待确认?)更多的答案可能要读一下vm的实现代码了。
static Thread currentThread();
static boolean interrupted();
static void sleep(...);
static void yield();
ThreadGroup的主要目的是将多个Thread对象组织起来,便于操纵。一个ThreadGroup即可以包括多个Thread对象,也可以包含多个ThreadGroup,从而构成树状结构。向上遍历使用getParent(),向下遍历使用enumerate()。
int activeCount()
返回ThreadGroup中活动Thread的数目(包括孙子等)。
int activeGroupCount()
返回ThreadGroup中活动子Group的数目(包括孙子等)。
ThreadLocal提供了一个变量针对多个thread存储各自对象的功能, 这个同win32中的TLS概念类似.
ThreadLocal有4个方法:
protected T initialValue()
public T get()
public void set(T value)
public void remove()
其中get()和set()分别是读和写. initialValue()是初始化操作, 两种情况下, initialValue()会被调用来执行初始化. 一种是, 第一次调用get(),如果之前未set(), 则initialValue()会被调用一次执行初始化, 后面的get()就不再调用initialValue()了. 第二种情况, remove()后, 直接调用get(). ThreadLocal的子类可以重新实现initialValue()实现自己特殊的初始化操作.
InheritableThreadLocal增加了一个方法childValue(), 子线程创建前, 在母线程中, childValue()将被调用来决定子线程中的InheritableThreadLocal的初始值. 籍此, 母线程可以控制子线程的InheritableThreadLocal的初始值.
TBD
1. JLS
2. Cay
3. Sun Microsystems, Inc, The Java Tutorial