线程间的通信
1、当任务相互之间进行协作时,
关键问题是实现这些任务之间的握手,为了实现这种握手,可以使用任务相同的基础特性:互斥;对于这些实现这些握手问题可以采用的方法:
①通过Object对象的wait()和notify()方法来安全实现;
②通过SE5并发类库中Condition对象的await()和signal()方法;
2、Object的wait、notify方法
1)wait、notify和sleep、yield的区别
①wait()可以使一个任务被挂起,
对象上的锁被释放,而
sleep、yield调用时
对象上的锁不会被释放;
②wait、notify是基于Object的,而sleep、yield是基于Thread的;
③wait、notify只能在synchronized同步控制方法、或同步控制块中调用(因为需要同步控制锁),sleep、yield可以在费同步控制块中调用;
2)wait(milliseconds):调用对象的锁被挂起milliseconds, 之后对象从wait中恢复;
wait():调用对象的锁被释放,并无限等待下去,直到该对象调用了notify或notifyAll;
notifyAll():将调用对象上全部被挂起的任务恢复运行;
notify():将调用对象上一个被挂起的任务恢复,该任务的选择时任意,一般当多个任务使用相同的条件等待同一个锁,此时使用notify比起notifyAll效率更高;
3)示例代码
T1:
synchronized(sharedMonitor){
<setup condition for T2>;
sharedMonitor.notify();
}
T2:
synchronized(shareedMonitor){
while(somwCondition){
//point1;
shareMonitor.wait();
}
}
//T1是通知T2的线程,T2在运行是由于某种条件而挂起,而T1进行相应的处理后改变该条件,同时释放shareMonitor的锁,是T1恢复运行;
/*如果T2是以下的方式,可能会错失T1的通知,在T2 point1处,由T1处理后的条件状态可能已经被修改,从而可能会导致死锁
while(someCondition){
//point1;
synchronized(sharedMonitor)
shareMonitor.wait();
}*/
实例:仿真延时多输线程输入开关(响应每一个任务):
class Light{
private boolean switch = false; //线程控制条件
public void turnOn() throws InterruptedException{ //turn on switch
synchronized(this){
while(switch == true)
wait();
TimeUnit.SECONDS.sleep(2); //at lest run 3s;
switch = true;
notifyAll();
}
}
public void trunOff() throws InterruptedException{ //turn off switch
synchronized(this){
while(switch == false)
wait();
switch = true;
notify();
}
}
}
//Test
class Demo{
public static void main() throws InterruptedException {
Switch lightSwitch = new Switch();
Scanner input = new Scanner(System.in);
ExecutorService exec = Executor.newCachedThreadPool();
while(!Thread.interrupted()){
in = nextBoolean();
if(in){
exec.execute(new Runnable(){
public void run(){
lightSwitch.turnOn();
}});
}
else if(!in){
exec.execute(new Runnable(){
public void run(){
lightSwitch.turnOff();
}});
}
}
}
}
3、Condition的await、signal操控Lock的行为
1)对lock绑定一个或多个Condition条件类,在不同的代码块中通过Condition来操作lock;
一个lock可以对应拥有多个condition,每个condition的状态对应各自的状态控制,共同决定lock的状态;
2)使用lock的condition条件进行线程通信时注意要检测
InterruptedException异常;
3) signal( ):唤醒任意一个等待该条件的线程;
signalAll( ):唤醒所有等待该条件的线程;
4)实例代码
class Demo {
*
Lock lock = new ReentrantLock();
*
Condition condition = lock.newCondition();
* public void method1(){
*
lock.lock();
* try{
*
while()
* condition.await();
* statements;
* }catch(
InterruptedException ex){
* }finally{
*
lock.unlock();
* }
* }
* public void method2(){
*
lock.lock();
* try{
* statements;
*
condition.signalAll();
* }finally{
*
lock.unlock();
* }
* }
* }
/*method1在条件不满时线程被挂起,对象锁释放,
method2获取对象锁之后,进行相应的操作修改条件,并释放对象锁
method1再次获取对象锁后,满足条件并执行之后的代码;*/
实例:仿真延时多输线程输入开关(响应每一个任务):
class Switch{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private boolean switch;
public void turnOn(){
lock.lock();
try{
while(switch == true)
condition.await();
TimeUnit.SECONDS.sleep(2);
switch = true;
condition.signalAll();
}catch(InterruptException e){
}finally{
lock.unlock();
}
}
public void turnOff(){
lock.lock();
try{
while(switch == false)
condition.await();
switch = false;
condition.signalAll();
}catch(InterruptException e){
}finally{
lock.unlock();
}
}
}
4、阻塞队列BlockingQueue<T>
1)wait和notifyAll是以一种比较底层的方式解决任务互操作的问题,
可以使用java.util.concurrent.BlockingQueue同步队列这种更高抽象级别的方式来解决这样的问题;
2)BlockingQueue在任何时刻都只允许一个任务加入或删除元素时,即能维持线程同步,
BlockingQueue为线程安全的;
3)BlockingQueue的主要实现具体类:
①ArrayBlockingQueue:有界阻塞队列,当满队时,加入操作的任务会被阻塞,但队空时,删除操作的任务会被阻塞;
②LinkedBlockingQueue:无界阻塞队列;
4)BlockingQueue很适合用来实现生产者-消费者模型;
5)实例
//生产者-消费者模型
public class Producer implements Runnable{
BlockingQueue<String> queue;
public Producer(queue){ this.queue = queue;}
public void run(){
try{
<statements to product>
String produce = ....;
queue.put(produce);
}catch(InterruptedException e){
<statements while task is blocked>
}
}
}
public class Consumer implements Runnable{
BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue){ this.queue = queue}
public void run(){
try{
String produce = queue.take();
<statements to use produce>
}catch(InterruptException e){
<statements whlie the task is blocked>
}
}
}
public class Test{
public static void main(){
ExecuteService exec = Executors.newCachedThradPool();
BlockingQueue<String> queue = new LinkedBlockingQueue();
exec.execute(new Producer(queue));
exec.execute(new Consumer(queue));
exec.execute(new Consumer(queue));
exec.shutdown();
}
}
5、任务间使用管道进行输入/输出
1)在Java中
PipedWriter、PipedReader类库提供线程功能以“管道”的形式对线程间的输入/输出提供了支持;
PipedWriter:允许不同任务任务向管道写入;
PipedReader:允许不同任务从同一个管道读取;
2)示例代码:
import java,io.*;
class Sender implementouts Runnable{
private PipedWriter out = new PipedWriter();
public void run(){
while(true){
out.write('A'+new Random().nextInt(26));
}catch(IOException e){
}catch(InterruptedException e){
}
}
}
class Receiver implements Runnable{
private PipedReader in = new PipedReader();
private static count;
public void run(){
while(true){
char get = (char)in.read();
System.out.println("Receiver "+ count++ +" Read:"+ get);
}catch(IOException e){
}catch(InterrupedException e){
}
}
}
class Test{
public static void main(){
ExecutorServcie exec = Executors.newCachedThreadPool();
exec.execute(new Sender());
exec.execute(new Revicer());
exec.execute(new Revicer());
TimeUnit.SECONDS.sleep(20);
exec.shutdownNow();
}
}