创建线程
扩展thread类
class MyThread extends Thread { public void run() { //task code } } ... MyThread th = new MyThread(); th.start();实现runnable接口
class MyThread implements Runnable { public void run() { //task code } } ... MyThread r = new MyThread(); Thread th = new Thread(r); //可以把任何实现了runnable的对象传给Thread的构造器(赋给其中的target域) th.start();
推荐使用第二种方式来创建线程。因为如果通过扩展Thread来创建线程,那么可任务与运行机制是绑定在一起的,而使用第二种方式,可以解耦合。另外,更重要的是,还可以实现资源共享,在第二种方式里,可以把同一个任务传进不同的Thread实例,这样就可以共享彼此任务中的资源,而这点第一种方式是做不到的。
中断线程
当对一个线程调用 interrupt 方法时,线程的中断状态将被置位。这是每一个线程都具有 的 boolean 标志。每个线程都应该不时地检査这个标志, 以判断线程是否被中断。
但是, 如果线程被阻塞, 就无法检测中断状态。这是产生 InterruptedExceptioii 异常的地 方。当在一个被阻塞的线程(调用 sleep 或 wait) 上调用 interrupt 方法时,阻塞调用将会被InterruptedException 异常中断。如果在每次工作迭代之后都调用 sleep 方法(或者其他的可中断方法,) islnterrupted 检测 既没有必要也没有用处。这种情况下应当去捕捉或抛出异常,做出相应的处理,而不应当忽略。
事实上,运行中的线程被中断,目的是 为了让其他线程获得运行机会。
线程状态
新创建线程
当用 new 操作符创建一个新线程时,如 newThread(r), 该线程还没有开始运行。
可运行线程
一旦调用 start 方法,线程处于 runnable 状态。一个可运行的线桿可能正在运行也可能没有运行, 这取决于操作系统给线程提供运行的时间。
抢占式调度系 统给每一个可运行线程一个时间片来执行任务。当时间片用完,操作系统剥夺该线程的运行 权,并给另一个线程运行机会。当选择下一个线程时, 操作系统考虑线程的优先级。
被阻塞线程
当一个线程试图获取一个内部的对象锁(而不是 java.util.concurrent 库中的锁), 而该锁被其他线程持有, 则该线程进人阻塞状态。当所有其他线程释放该锁,并且线程调度器允许 本线程持有它的时候,该线程将变成非阻塞状态。
等待线程
当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。在调用 Object.wait 方法或 Thread.join 方法, 或者是等待 java.util.concurrent 库中的 Lock 或 Condition 时,就会出现这种情况。
有几个方法有一个超时参数。调用它们导致线程进人计时等待( timed waiting ) 状 态。这一状态将一直保持到超时期满或者接收到适当的通知。如Thread.sleep,Object.wait,Thread.join。
当一个线 程被阻塞或等待时(或终止时), 另一个线程被调度为运行状态。当一个线程被重新激活,转为可运行状态,等待调度。
被终止的线程
线程因如下两个原因之一而被终止:
- 因为 run 方法正常退出而自然死亡
- 因为一个没有捕获的异常终止了 run 方法而意外死亡。
特别是, 可以调用线程的 stop 方法杀死一个线程。 该方法抛出 ThreadDeath 错误对象, 由此杀死线程。但是,stop 方法已过时, 不要在自己的代码中调用这个方法。

线程属性
线程优先级
每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以用 setPriority 方法提高或降低任何一个线程的优先级。可以将优先级设置为在 MIN_PRIORITY (在 Thread 类中定义为 1 ) 与 MAX_PRIORITY (定义为 10 ) 之间的任何值。NORM_PRIORITY 被定义为5。
守护线程
可以通过调用 t.setDaemon(true); 将线程转换为守护线程(daemon thread。) 这样一个线程没有什么神奇。守护线程的唯一用途是为其他线程提供服务。
守护线程应该永远不去访问固有资源, 如文件、 数据库,因为它会在任何时 候甚至在一个操作的中间发生中断。
同步
锁对象
private Lock bankLock = new ReentrantLock();
...
myLock.lock(); // a ReentrantLock object
try
{
...
}
finally
{
myLock.unlock();
/*把解锁操作括在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常,
锁必须被释放。否则, 其他线程将永远阻塞。*/
}
注意:如果使用锁, 就不能使用带资源的 try 语句。
锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数( hold count) 来跟踪对 lock 方法的嵌套调用。线程在每一次调用 lock 都要调用 unlock 来释放锁。由于这一特性, 被一个锁保护的代码可以调用另一个使用相同的锁的方法。
条件对象
通常, 线程进人临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对 象来管理那些已经获得了一个锁但是却不能做有用工作的线程。
例:
如果 transfer 方法发现余额不足,它调用 sufficientFunds.await(); 当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加账户余额的操作。
等待获得锁的线程和调用 await 方法的线程存在本质上的不同。一旦一个线程调用 await 方法, 它进人该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的 signalAll 方法时为止。(wait一般用于Synchronized中,而await只能用于ReentrantLock锁中)
private Condition sufficientFunds;
...
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try{
while(accounts[from] < amount)
sufficientFunds.await();
sufficientFunds.signalAll();
/*这一调用重新激活因为这一条件而等待的所有线程。当这些线程从等待集当中移出时,
它们再次成为可运行的,调度器将再次激活它们。同时, 它们将试图重新进人该对象。一旦
锁成为可用的,它们中的某个将从 await 调用返回, 获得该锁并从被阻塞的地方继续执行。*/
}
finally{
bankLock.unlock();
}
}至关重要的是最终需要某个其他线程调用 signalAll 方法。当一个线程调用 await 时,它 没有办法重新激活自身。它寄希望于其他线程。如果没有其他线程来重新激活等待的线程,它就永远不再运行了。这将导致令人不快的死锁( deadlock) 现象。如果所有其他线程被阻 塞,最后一个活动线程在解除其他线程的阻塞状态之前就调用 await 方法,那么它也被阻塞。 没有任何线程可以解除其他线程的阻塞,那么该程序就挂起了。
应该何时调用 signalAll 呢? 经验上讲, 在对象的状态有利于等待线程的方向改变时调用 signalAll。例如, 当一个账户余额发生改变时,等待的线程会应该有机会检查余额。
注意调用 signalAll 不会立即激活一个等待线程。它仅仅解除等待线程的阻塞, 以便这些 线程可以在当前线程退出同步方法之后,通过竞争实现对对象的访问。
另一个方法 signal, 则是随机解除等待集中某个线程的阻塞状态。这比解除所有线程的 阻塞更加有效,但也存在危险。如果随机选择的线程发现自己仍然不能运行, 那么它再次被 阻塞。如果没有其他线程再次调用 signal, 那么系统就死锁了。
synchronized关键字
如果一个方法用 synchronized关键字声明,那么对象的锁 将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
public synchronized void method()
{
method body
}等价于
public void method()
{
this.mylock.1ock();
try{
method body
}finally{
this.mylock.unlock();
}
}
内部对象锁只有一个相关条件。wait 方法添加一个线程到等待集中,notifyAll/notify方 法解除等待线程的阻塞状态。换句话说,调用 wait 或 notityAll 等价于
theCondition.await();
theCondition.signalAll();将静态方法声明为 synchronized 也是合法的。如果调用这种方法,该方法获得相关的类对 象的内部锁。例如,如果 Bank 类有一个静态同步的方法,那么当该方法被调用时,Bank.class 对象的锁被锁住。因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。
内部锁和条件存在一些局限。包括:
不能中断一个正在试图获得锁的线程。
试图获得锁时不能设定超时。
每个锁仅有单一的条件, 可能是不够的。
如果 synchronized 关键字适合你的程序, 那么请尽量使用它,这样可以减少编写的代 码数量,减少出错的几率。
同步阻塞
线程可以通过调用同步方法获得锁。还有 另一种机制可以获得锁,通过进入一个同步阻塞。当线程进入如下形式的阻塞:
synchronized (obj)
{
critical section
}
//于是他获得obj的锁有时程序员使用一个对象的锁来实现额外的原子操作, 实际上称为客户端锁定。这样可以保证当一个线程运行时,某一个存储位置不会把另一个线程存入另一个值。
synchronized (accounts)
{
accounts.set(from, accounts.get(from)- amount):
//在第一次对get的调用已经完成之后,该线程完全可能被剥夺运行权
accounts.set(to, accounts.get(to) + amount);
}
这个方法可以工作,但是它完全依赖于这样一个事实, Vector 类对自己的所有可修改方法都使用内部锁。所以说,客户端锁定是非常脆弱 的,通常不推荐使用。
Volatile域
有时,仅仅为了读写一个或两个实例域就使用同步, 显得开销过大了。
volatile 关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为 volatile , 那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
private volatile boolean done;
public boolean isDone() { return done; }
public void setDone() { done = true; }//不能提供原子性