码迷,mamicode.com
首页 > 编程语言 > 详细

Java并发编程(八)线程间协作(上)

时间:2018-06-23 20:52:14      阅读:173      评论:0      收藏:0      [点我收藏+]

标签:finally   []   ant   worker   java   boolean   pool   通知   方式   

多线程并发执行时,不同的线程执行的内容之间可能存在一些依赖关系,比如线程一执行a()方法和c()方法,线程二执行b()方法,方法a()必须在方法b()之前执行,而方法c()必须在方法b()之后执行。这时两个线程之间就需要协作才能完成这个任务,使两个线程协作有一个简单粗暴的方法,即监控布尔变量,代码如下:

boolean finishA = false;
boolean finishB = false;

线程一执行下面的代码:

a();
finishA = true;
while(!finishB){}
c();

线程二执行下面的代码:

while(!finishA){}
b();
finishB = true;

在执行b()方法和c()方法时都会检查依赖的方法是否执行结束,只有依赖的方法执行结束才跳出循环。这种方法的优点是简单粗暴,缺点是在等待依赖的方法时线程处于忙等待的状态,即线程处于运行状态(占用CPU时间)但是没有做任何有实际意义的东西,更好的办法是在线程等待时将其阻塞,阻塞状态时不占用CPU时间,从而提高CPU的利用率。

使用内置锁协作

Java提供了线程间合作的机制,即Object.wait()方法、Object.notify()和Object.notifyAll()方法。

wait()方法:使当前线程阻塞,等待其它线程调用notify()方法,释放当前获取的锁。

notify()方法:唤醒一个等待着的线程,这个线程唤醒之后尝试获取锁,其它线程继续等待。

notifyAll()方法:唤醒所有等待着的线程尝试获取锁,这些线程排队等待锁。

使用这些方法举个小例子,学生去食堂吃饭的时候先取一碗,然后把碗交给盛饭阿姨,阿姨盛完饭把碗还给同学,这时候同学就可以吃饭了,用代码模拟这个例子如下:

class Student implements Runnable {
    public void run() {
        synchronized(CafeteriaTest.wan) {
            System.out.println("学生:取到了一个碗");
            System.out.println("学生:阿姨帮忙盛饭");
            CafeteriaTest.wan.notify();
            try {
                CafeteriaTest.wan.wait();
            } catch (InterruptedException e) { }
            System.out.println("学生:吃饭");
        }
    }
}
class CafeteriaWorker implements Runnable {
    public void run() {
        synchronized(CafeteriaTest.wan) {
            try {
                CafeteriaTest.wan.wait();
            } catch (InterruptedException e) { }
            System.out.println("阿姨:给学生盛饭");
            CafeteriaTest.wan.notify();
        }
    }
}
public class CafeteriaTest {
    public static Object wan = new Object();
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new CafeteriaWorker());
        Thread.sleep(100);//等阿姨准备好
        exec.execute(new Student());
        exec.shutdown();
    }
}

输出结果如下:

学生:取到了一个碗

学生:阿姨帮忙盛饭

阿姨:给学生盛饭

学生:吃饭

例子中先创建了一个“阿姨线程”,这个线程先获取“碗”的锁,然后调用了wait()方法进入阻塞状态并释放了锁。接着我们创建了“学生线程”,学生先打印取到了碗,然后调用notify()方法通知“阿姨线程”盛饭,并且调用wait()方法使当前线程释放锁并阻塞;随后“阿姨线程”从阻塞状态恢复为学生打饭,然后“阿姨线程”调用notify()方法通知学生打完饭了,“阿姨线程”运行结束并释放了锁,“学生线程”拿到了“碗的锁”开始吃饭。

在这个过程中有三点需要注意:

1.在调用wait()和notify()方法之前必须使用synchronized关键字获取这个对象的锁,否则系统会抛异常。因此不能在使用显示锁的临界区内调用这些方法。

2.调用wait()方法之后有两个因素阻止线程执行:1.线程由于等待notify()方法而处于阻塞状态。2.获得notify()方法的通知后,尝试获取锁,此时锁有可能是不可用的,因此会等待其它线程释放锁,而使线程阻塞。

3.一定要让“阿姨线程”先拿到锁,如果“学生线程”先拿到锁,“阿姨线程”会由于拿不到锁而被阻塞,直到“学生线程”执行到wait()方法;但在这之前已经调用了notify()方法了,而“阿姨线程”没有执行到wait()方法,错过了“学生线程”发来的信号。

使用显示锁协作

调用一个对象的wait()、notify()方法之前必须获得这个对象的锁,但是使用显示的锁时不能获取某个特定对象的锁,因此也就不能在显示锁的临界区内使用这些方法。显示锁为我们提供了另一种类似wait()和notify()方法的线程协作机制,使用起来与wait()和notify()方法完全相同,我们用这种方式来改写学生打饭的例子:

class Student implements Runnable {
    public void run() {
        CafeteriaLockTest.lock.lock();
        try{
            System.out.println("学生:取到了一个碗");
            System.out.println("学生:阿姨帮忙盛饭");
            CafeteriaLockTest.wan.signal();
            CafeteriaLockTest.wan.await();
            System.out.println("学生:吃饭");
        } catch (InterruptedException e) { }
        finally {
            CafeteriaLockTest.lock.unlock();
        }
    }
}
class CafeteriaWorker implements Runnable {
    public void run() {
        CafeteriaLockTest.lock.lock();
        try {
            CafeteriaLockTest.wan.await();
            System.out.println("阿姨:给学生盛饭");
            CafeteriaLockTest.wan.signal();
        }
        catch (InterruptedException e) { }
        finally {
            CafeteriaLockTest.lock.unlock();
        }
    }
}
public class CafeteriaLockTest {
    public static Lock lock = new ReentrantLock();
    public static Condition wan;
    public static void main(String[] args) throws InterruptedException {
        wan = lock.newCondition();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new CafeteriaWorker());
        Thread.sleep(100);//等阿姨准备好
        exec.execute(new Student());
        exec.shutdown();
    }
}

输出结果如下:

学生:取到了一个碗

学生:阿姨帮忙盛饭

阿姨:给学生盛饭

学生:吃饭

在代码中我们定义了一个重入锁的对象作为两个线程共用的锁,又调用lock.newCondition()方法获取一个Condition对象用来实现多线程协作,Condition的await()方法相当于Object的wait()方法,signal()方法相当于Object的notify()方法,Condition还有一个signalAll()方法相当于Object的notifyAll()方法。有个需要注意的地方就是,在调用Condition的await()方法时不要误用wait()方法。

总结

本节讲了如何让多个线程协作完成某项任务,其中wait()方法和之前讲过的Thread.sleep()方法类似,两者都使线程处于阻塞状态,但wait()方法要求调用之前必须获取对象的内置锁,sleep()方法调用时没有前置条件;另一个区别是wait()方法调用后会释放对象的锁,而sleep()方法不释放锁。线程间的协作未完待续。

公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。

Java并发编程(八)线程间协作(上)

标签:finally   []   ant   worker   java   boolean   pool   通知   方式   

原文地址:https://www.cnblogs.com/victorwux/p/9218348.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!