线程协作

线程之间的协作 我们已经知道了在使用线程同时运行多个任务可以通过使用锁来同步,从而使得一个任务不会干涉另一个任务的资源,这个问题已经解决了,下一步是学习如何使用任务彼此之间可以协作,从而使得多个任务可以一起去工作解决某个问题,现在的问题不是彼此之间的干涉而是彼此之间的协调,因为在这类问题中,某些问题必须在其它部分被解决之前解决,
那么任务之间的协调最重要的就是 任务之间的握手,任务之间的交接,互斥保证了只有一个任务可以响应某个信号,在互斥之上我们添加了一种途径,可以将自身挂起直至外部某些条件发生变化,表示这个任务可以继续进行向前开动下去了,这片文章主要浏览任务之间的握手问题,这种握手问题可以通过objcet 的方法 wait和notify来安全的实现,jse5又提供了新的具有await()和singal的Condition对象 ,
首先看下Object 的Wait 和 notify
首先这个是objcet的方法 因为锁是对象的一部分 由于wait要操作对象的锁,它的功能是挂起当前执行的线程 并且释放该对象的互斥锁 所以必须要在同步块中 已经获取到该对象的同步锁才可以 ,所以只能在同步代码中使用,虽然在外面使用可以通过编译 但是运行时会抛出此线程不是该对象锁的占有者的错误。
我们知道 在调用sleep和 yeild的时候 是不会释放线程获取到对象的互斥锁的。而wait是释放线程占有对象的互斥锁的,这也就意味着其他线程可以任意获取该对象的锁 访问其他同步的方法,这些其他方法通常会使该对象反生变化 而这些变化也是使挂起的线程被重新唤起的前置条件。
wait() 是有两种形式的
一)接收毫秒参数,含义和sleep方法里的参数是差不多的,都是指在此期间暂停,和sleep不同的是
1) wait期间 对象锁是释放的
2)可以通过notify和notifyAll 或者令时间到期,从挂起的阻塞中恢复至就需状态改。
二)不接受参数,也是更常用形式的wait,这种形式的wait 会让那个线程无限制的挂起 直到 线程接收到notify或者notifyAll消息
 
注意:可以让另一个对象执行某种操作来维护自己的锁,如果你要这么做的话 必须得到这个对象的锁
比如
sychronized(x){
x.notifyAll();
}
下面就演示一个事例
一辆车 要完成一辆车的打蜡和抛光的任务
要求 首先打蜡 然后 才可以 抛光 然后在抛光之后才可以继续打蜡
package test.thread.coordinate.Object;

import java.util.concurrent.TimeUnit;

/**
 * 基本任务
 * 任务描述 汽车涂蜡 
 * 汽车打蜡之后 才可以抛光 而涂蜡任务在涂另一层蜡之前必须在抛光之后
 *  
 * @author Administrator
 *
 */
public class Car {
    /**
     * 打蜡-抛光状态
     * false 可以涂蜡
     * true 可以抛光
     */
    private Boolean waxOn = false;
    /**
     * flase 是可以打蜡 打蜡之后 修改为true 表示可以抛光
     */
    public synchronized void waxed() {
        waxOn = true;
        this.notifyAll();
    }
    /**
     * 抛光 true 为可以抛光 抛光完成 修改状态值为false 表示可以打蜡 
     */
    public synchronized void buffed() {
        waxOn = false;
        this.notifyAll();
    }
    /**
     * 检查状态   刚抛光完成状态值为false  若刚抛光完成则是线程挂起等待 打蜡
     * 这个应该是在抛光的任务之中负责将不可以抛光的任务线程挂起的
     * 由于抛光是在打蜡之后   
     * @throws InterruptedException
     */
    public synchronized void waitForWaxing() throws InterruptedException {
        while (waxOn == false){
            wait();
        }
    }
    /**
     * 检查状态 如果为true 则表示打蜡完成 可以抛光 线程挂起
     * 这个是用于在 打蜡中 在打蜡修改标识符后 负责将打蜡线程任务挂起的作用的
     * @throws InterruptedException
     */  
    public synchronized void waitForBuffing() throws InterruptedException{
        while(waxOn == true){
            wait();
        }
    }

}
class WaxOn implements Runnable{
    private Car car;
    public WaxOn(Car c){car = c;}
    
    @Override
    public void run() {
        try{
        while(!Thread.currentThread().isInterrupted()){
            System.out.println("开始打蜡!");
            TimeUnit.MICROSECONDS.sleep(200);
            /**
             * 打蜡
             */
            car.waxed();
            /**
             * 打蜡之后 必须等待抛光完成才可以 继续打蜡 所以线程挂起 等待  线程抛光 唤醒所有的挂起线程
             */
            /**
             * 打蜡结束 等待下一次打蜡  挂起线程   等待抛光 
             */
            car.waitForBuffing();
            
            
        }
        }catch(InterruptedException e){
            System.out.println("通过中断退出");
        }
        System.out.println("结束打蜡任务");
        
    }
    
}
class WaxOff implements Runnable{
    private Car car;
    public WaxOff(Car car) {
        this.car =  car;
        
    }
    @Override
    public void run() {
        try{
        while(!Thread.currentThread().isInterrupted()){
            //检查状态 值是否为false 是则表示刚抛光完成 可以打蜡 不可以则挂起所以在同时开始运行的时候是 抛光任务 先被挂起 等待打蜡结束
            
            System.out.println("抛光开始");
            TimeUnit.MICROSECONDS.sleep(200);
            /**
             * 抛光开始 修改状态值为false 标记为可以打蜡  并唤醒被挂起打蜡任务的线程 重新加入 争取对象锁的队列
             * 然后该任务在次获得对象锁的时候在判断是否可以打蜡     
             **/
            car.waitForWaxing();
            car.buffed();
            
            //抛光被挂起 并没有被唤醒 
        }}catch(InterruptedException e){
            System.out.println("通过中断退出");
        }
        System.out.println("抛光结束");
    }
    
}

测试类

package test.thread.coordinate.Object;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Waxmatic {
    public static void main(String[] args) {
        Car c = new Car();
        ExecutorService exec = Executors.newCachedThreadPool();

        exec.execute(new WaxOn(c));
        exec.execute(new WaxOff(c));
        

        try {
            TimeUnit.SECONDS.sleep(5);
            
        } catch (InterruptedException e) {

            
            System.exit(0);
        } finally {
            
            exec.shutdownNow();
        }

    }

}

 

WaxOn.run() 方法是给汽车打蜡的第一个步骤, 首先sleep将该任务阻塞 模拟打蜡的过程,然后 调用打蜡方法 将打蜡-抛光标识修改为true ,并且调用watiForBuffing(),这个方法就是检查标识是否可以继续可以打蜡 也就是检查标识 为刚打蜡结束时也就是 标识为true 就会挂起当前线程。并唤醒其他该对象上被挂起的所有线程 在这里其实就是唤醒抛光的线程

然后该线程一直等到 有其他方法唤醒。

然后线程WaxOff.run()方法 是相当于第二个步骤 因为线程是同步运行,虽然是加了同步控制块 但是 当WaxOff.run方法 若抢先一步抢占对象锁在车辆还没有打蜡的时候就被执行 ,但是这样是不符合逻辑的,为了防止这种情况 把在抛光结束之后 标识符被修改以后 才调用的检查标识符看是否可以继续抛光的方法 提前放到 没抛光之前进行检查 ,若可以抛光则进行抛光,然后while 循环回来 再次检查标识符状态,若不可以继续抛光则线程被挂起 并唤醒所有挂起线程 其实就是唤醒了 打蜡被挂起的任务线程

我们再利用wait 和 notify 或者notifyAll 这种方式进行线程之间的协作,有可能因为 线程之间的切换 或者没有进行同步彻底的 时候 就会反生 死锁

例如

T1

sychronized(sharedMonitor){

   <setup condition for T2>(这代表防止T2调用wait的一个动作,当然前提是T2还没有被挂起)

   sharedMonitor.notifyAll();   

}

T2

while(someCondition){

// Point1

  sychronized(someCondition){

  sharedMonitor.wait();  

  }

}

假设T2 发现someCondition 为true 他开始执行下面的代码 但是在T1时 线程调度器 将执行线程切换为T1 T1中先一步执行唤醒方法,待到T2得以继续执行时 他不会知道T1已经没有唤醒他的机会了 他还是会进入 wait 然后陷入无限的等待 发生死锁

正确的T2应该是避免somecondition被竞争

sychronized(sharedMonitor){

while(someCondition){}  

  sharedMonitor.wait();

}  

}

 

posted @ 2017-11-29 09:06  陈东的博客  阅读(107)  评论(0编辑  收藏  举报