Java 并发编程知识点(三)

来源:互联网 发布:软件服务外包合同范本 编辑:程序博客网 时间:2024/04/30 08:19

1、通过wait方法可以使线程挂起,直到线程得到notify或notifyall(等价于SE5中的signal或signalall)。在较早的代码中你可能会看到suspend和resume来阻塞和唤醒线程,但是现代Java已经废弃了这些方法。stop方法也已经被废弃了,因为它不释放线程获得的锁,并且如果线程处于不一致的状态(受损状态),其他任务可以在这种状态下浏览并修改他们,这样的问题是微妙的难以被发现的。

2、Thread类包含interrupt()方法,因此你可以终止被阻塞的任务,这个方法将设置线程的中断状态。如果一个线程已经被阻塞了或者试图去执行一个阻塞的操作,那么设置这个中断状态将抛出interruptedexception异常。当抛出异常或者该任务调用Thread.interrupted()时,中断状态将被复位。正如你看到的,Thread.interrupted提供了离开run循环而不抛出异常的第二种方式。

3、线程池中断线程方式:a)终止所有线程:在executor上调用shutdownnow,那么它将发送一个interrupt调用给所有的线程。b)终止指定的线程:如果想终止指定的线程,则必须使用executor的submit方法来启动线程,该方法会返回一个future<?>,持有这个future的关键是你可以在其上调用cancel,因此cancel是一种中断由executor启动的单线程的方式。

4、可中断阻塞与不可中断阻塞:Sleep阻塞是一种可中断的阻塞,中断会抛出InterruptedException异常;而I/O与Synchronized阻塞是不可中断的,因此也不会抛出InterruptedException异常。简言之,你能够中断对sleep的调用(或者任何要求抛出InterruptedException的调用),但是你不能中断正在试图获取synchronized锁或者试图执行I/O操作的线程。

       那有的同学可能有疑问:在执行I/O任务的时候,不可中断则意味着I/O具有锁住你的多线程程序的潜在的可能?确实存在。解决方案虽然略显笨拙但是确实行之有效 -- 关闭任务在其上发生阻塞的底层资源。比如System.in等待一个输入,在到达指定的条件之后(一定的时间之后),通过System.in,close()就可以了;一旦底层的资源被关闭,任务将解除阻塞。

        幸运的是,Java提供的各种nio类提供了更人性化的I/O中断,被阻塞的nio通道会自动的响应中断。

        Java SE5并发类库中添加了一个新特性,即在ReentrantLock上阻塞的任务是可以被中断的。

5、检查中断:当你在线程上调用interrupt()时,中断发生的唯一的时刻是在任务要进入阻塞到阻塞操作或者已经在阻塞操作内部时。   如果你调用interrupt以停止某个任务,而碰巧run()并没有产生任何阻塞调用的情况,你就无法总是可以离开run()循环了。此时你可以选择第二种方式退出:interrupted(),该方法不仅可以告诉你interrupt是否被调用过,而且还可以清除中断状态。在异常捕获的时候,保持如下结构是一个好的习惯:

try{

try{


}finally{


}

}catch(...){


}

6、线程间的协作(共享资源):wait与notify或notifyall。调用sleep的时候,锁并没有释放,调用yield也属于这种情况,理解这一点很重要。而wait调用的时候将释放锁,任务挂起。另外一个需要注意的地方是只能在同步控制方法或者同步块中调用wait与notify或notifyall,因为调用wait与notify或notifyall这些方法前必须拥有对象的锁。

class Car{

private boolean waxOn  = false;

public synchronized void waxed(){

waxOn  = true;

notifyAll();

}

public synchronized void buffed(){

waxOn  = false;

notifyAll();

}

public synchronized void waitForWaxing() thorws InterruptedException{ 

while(waxOn == false)

wait();

}

public synchronized void waitForBuffing() thorws InterruptedException{ 

while(waxOn == true)

wait();

}

}

如上,必须用一个检查感兴趣的条件的while循环包围wait()。这很重要。

7、错失的信号:当两个线程使用notify/wait或notifyall/wait进行协作 的时候,有可能会错失某种信号。假设T1是通知T2的线程,而这两个线程都是使用下面(有缺陷)方式实现的:

T1:

synchronized (shareMonitor){

<setUp Condition for T2>

shareMonitor.notify();

}

T2:

while(someCondition){

//point1

synchronized (shareMonitor){

shareMonitor.wait();

}

}

假设T2计算someCondition为true。在point1,线程调度器切换到了T1,T1执行某些设置,然后调用notify。此时T2得以继续执行,但是此时对于T2来说并未感知到变化(因为没有wait),此时死锁发生了。因此正确的写法是:

synchronized (shareMonitor){

while(someCondition)

shareMonitor.wait();

}

8、notify与notifyall

从技术上来说,可能会存在多个任务在单个对象上处于wait状态,因此调用notifyall比只调用notify更安全。有的时候,使用notify而不是notifyall是一种优化。使用notify时,在众多等待同一个锁的任务中只有一个会被唤醒,因此如果你希望使用notify,就必须保证被唤醒的是恰当的任务。为了使用notify,所有的任务必须等待相同的条件,因为如果你有多个任务在等待不同的条件,那么你就不知道是否唤醒了恰当的任务。如果使用notify,当条件变化时,必须只有一个任务能够从中受益。这些限制对所有可能的子类必须总是起作用。否则就应该使用notifyall而不是notify。

注意:notifyall并不意味着所有处于wait状态的任务均被唤醒,而是当notifyall因某个特定的锁被调用时,只有等待这个锁的任务才会被唤醒。

9、使用显示的Lock和Condition

private Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

lock.lock();

condition.signalall();

lock.unlock();

--------------------------------------------

lock.lock();

condition.await();

lock.unlock();

惯用方式如上所示,使用互斥并允许挂起的基本类Condition,通过await挂起一个任务,通过signalAll(或者signal)唤醒任务。与使用notifyall相比,signalAll更安全。

10、使用wait和notifyall解决线程间 的协作是一种非常低级的方式。为此,我们可以使用同步队列来解决线程间的协作问题。在java.util.concurrent.BlockingQueue中提供了这个队列,比如LinkedBlockingQueue和ArrayBlockingQueue等。如果消费者任务试图从队列中获取对象,而此队列是空的,那么这些队列可以挂起消费者任务,并且当有更多的元素可用时恢复消费者任务。阻塞队列可以解决非常大量的问题,而其方式与wait和notifyall相比,则简单并可靠的多。
0 0
原创粉丝点击