java多线程(3):Lock接口和Condition监视器接口使用详解

来源:互联网 发布:网络有初心 编辑:程序博客网 时间:2024/05/29 14:54

前言

前面使用的synchronized关键字来保证同步时,每个锁对象都有自己的一套监视器方法(wait,notify,notifyAll),这些方法是继承自Object类的。也就是说,锁对象被实例化后,只有一套监视器方法,一个线程要唤醒其他线程只能使用notifyAll方法,这也是多线程中的效率问题的根源。

为此,jdk1.5引入了java.util.concurrent.locks包,并提供了Lock和Condition接口及实现类。下面来看这两个接口的jdk文档。

  • Lock 实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
  • Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object监视器方法的使用。

这两个接口将“锁对象”和“监视器对象”分开,这样,一个“锁”就可以关联多个“监视器对象”。也就能实现生产者线程固定唤醒消费者线程,消费者线程固定唤醒生产者线程。

正文

一,Lock和Condition接口的组合使用

我们来修改上一篇中的多生产者和多消费者代码。

package com.jimmy.ThreadCommunication;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class Resource3{  // 资源函数    private String productName;  // 共享资源不变    private int count = 1;    private Lock lock = new ReentrantLock(); // 定义一个锁对象    private Condition conProducer = lock.newCondition(); // 获得lock锁的"生产者"线程监视器对象    private Condition conConsumer = lock.newCondition(); // 获得lock锁的"消费者"线程监视器对象    private boolean flag = false;  // 资源类增加一个标志位,默认false,也就是没有资源    public void produce(String name){        lock.lock(); // 获取锁        try {  // 业务代码要写在try块中            while (flag == true) {  // flag判断不变,即如果flag为true,也就是有资源了,生产者线程就去等待。                try {                    conProducer.await();  // "生产者"线程等待                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            this.productName = name + count;            count ++;            System.out.println(Thread.currentThread().getName()+"....生产者.."+this.productName);            flag = true;  // 生产完了就将flag修改为true            conConsumer.signal();  // 随机唤醒"消费者"线程中被await的一个线程        }         finally        {            lock.unlock();  // 无论如何都要释放锁        }    }    public void consume() {        lock.lock();        try {            while (flag == false) {                  try {                                        conConsumer.await();                        } catch (InterruptedException e) {                    e.printStackTrace();                }            }            System.out.println(Thread.currentThread().getName()+"...消费者.."+this.productName);                   flag = false;  // 消费完了就把标志改为false            conProducer.signal();  // 随机唤醒"生产者"线程中被await的一个线程        } finally {            lock.unlock();  // 无论如何,最后要释放锁        }    }}class Producer3 implements Runnable{   // 生产者类不变    private Resource3 res;    //生产者初始化就要分配资源    public Producer3(Resource3 res) {        this.res = res;    }    @Override    public void run() {        for (int i = 0; i < 5; i++) {            res.produce("bread");        }    }}class Comsumer3 implements Runnable{   // 消费者类不变    private Resource3 res;    //同理,消费者一初始化也要分配资源    public Comsumer3(Resource3 res) {        this.res = res;    }    @Override    public void run() {        for (int i = 0; i < 10; i++) {            res.consume();        }    }}public class ProducerAndConsumer3 {    public static void main(String[] args) {  // 测试程序不变        Resource3 resource = new Resource3();  // 实例化资源        Producer3 producer = new Producer3(resource); // 实例化生产者,并传入资源对象        Comsumer3 comsumer = new Comsumer3(resource); // 实例化消费者,并传入相同的资源对象        Thread threadProducer1 = new Thread(producer); // 创建2个生产者线程        Thread threadProducer2 = new Thread(producer);        Thread threadComsumer1 = new Thread(comsumer); // 创建2个消费者线程        Thread threadComsumer2 = new Thread(comsumer);        threadProducer1.start();        threadProducer2.start();        threadComsumer1.start();        threadComsumer2.start();    }}

代码与上一篇博客中相比,Lock.lock()和Lock.unlock()代替了“synchronized”关键字,而且Lock的使用更加灵活,也更加“面向对象”。Condition.await()/Condition.signal()/Condition.signalAll()方法替代了以前的this.await()/this.signal()/this.signalAll()方法,更重要的是,Lock和Condition分开,而且一个Lock可以绑定多个Condition,这就为唤醒对方线程打下了基础,从而提高了多线程的执行效率。

二,多资源的情况

前面写的代码都是多个线程对单一资源的操作,实际中资源会放在一个集合中,生产者不停的往里面放,消费者不停的从里面取,下面就是将资源放进数组中循环存取的示例代码。

package com.jimmy.ThreadCommunication;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class BoundedBuffer {    final Lock lock = new ReentrantLock();  // 获得"锁"对象     final Condition notFull = lock.newCondition(); // 得到锁对象的监视器对象    final Condition notEmpty = lock.newCondition();    final Object[] items = new Object[50]; // 资源放进数组中循环存取    int putptr; // 生产者脚标    int takeptr; // 消费者脚标    int count;  // 资源总数    public void put(Object x) throws InterruptedException {        lock.lock(); // 一进来就锁上        try {            while (count == items.length) // 生产者判断组数满了就等待                notFull.await();             items[putptr] = x;            if (++putptr == items.length) // 脚标到头就又从头开始                putptr = 0;            ++count;            System.out.println(Thread.currentThread().getName()+"...生产..No "+putptr+"..剩余 "+count);            notEmpty.signal();  // 唤醒消费者线程        } finally {            lock.unlock();  // 最终一定要释放锁        }    }    public Object take() throws InterruptedException { // 注释跟上面函数的差不多        lock.lock();        try {            while (count == 0)                notEmpty.await();            Object x = items[takeptr];            if (++takeptr == items.length)                takeptr = 0;            --count;            System.out.println(Thread.currentThread().getName()+"..消费..No "+takeptr+"..剩余 "+count);            notFull.signal();            return x;        } finally {            lock.unlock();        }    }}class Producer4 implements Runnable{    private BoundedBuffer buffer;    public Producer4(BoundedBuffer buffer) {        this.buffer = buffer;    }    @Override    public void run() {        int i = 0;        while(i < 200) {            try {                buffer.put("bread");                i++;            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}class Consumer4 implements Runnable{    private BoundedBuffer buffer;    public Consumer4(BoundedBuffer buffer) {        this.buffer = buffer;    }    @Override    public void run() {        int i = 0;        while(i < 200) {            try {                buffer.take();                i++;            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}public class ProducerAndConsumer4 {    public static void main(String[] args) {        BoundedBuffer boundedBuffer = new BoundedBuffer();        Producer4 producer4 = new Producer4(boundedBuffer);        Consumer4 consumer4 = new Consumer4(boundedBuffer);        Thread produceThread1 = new Thread(producer4);        Thread produceThread2 = new Thread(producer4);        Thread consumeThread1 = new Thread(consumer4);        Thread consumeThread2 = new Thread(consumer4);        produceThread1.start();        produceThread2.start();        consumeThread1.start();        consumeThread2.start();    }}

来看一下输出的结果,只截取了其中一部分:

Thread-1...生产..No 1..剩余 1Thread-1...生产..No 2..剩余 2Thread-1...生产..No 3..剩余 3Thread-1...生产..No 4..剩余 4Thread-1...生产..No 5..剩余 5Thread-1...生产..No 6..剩余 6Thread-1...生产..No 7..剩余 7Thread-1...生产..No 8..剩余 8Thread-1...生产..No 9..剩余 9Thread-1...生产..No 10..剩余 10Thread-1...生产..No 11..剩余 11Thread-1...生产..No 12..剩余 12Thread-1...生产..No 13..剩余 13Thread-1...生产..No 14..剩余 14Thread-1...生产..No 15..剩余 15Thread-1...生产..No 16..剩余 16Thread-2..消费..No 1..剩余 15Thread-2..消费..No 2..剩余 14Thread-2..消费..No 3..剩余 13Thread-2..消费..No 4..剩余 12Thread-2..消费..No 5..剩余 11Thread-2..消费..No 6..剩余 10Thread-2..消费..No 7..剩余 9Thread-2..消费..No 8..剩余 8Thread-2..消费..No 9..剩余 7Thread-2..消费..No 10..剩余 6Thread-2..消费..No 11..剩余 5Thread-2..消费..No 12..剩余 4Thread-2..消费..No 13..剩余 3Thread-2..消费..No 14..剩余 2Thread-2..消费..No 15..剩余 1Thread-2..消费..No 16..剩余 0Thread-0...生产..No 17..剩余 1Thread-0...生产..No 18..剩余 2Thread-0...生产..No 19..剩余 3Thread-0...生产..No 0..剩余 4Thread-0...生产..No 1..剩余 5Thread-0...生产..No 2..剩余 6Thread-0...生产..No 3..剩余 7Thread-0...生产..No 4..剩余 8Thread-0...生产..No 5..剩余 9Thread-0...生产..No 6..剩余 10Thread-0...生产..No 7..剩余 11Thread-0...生产..No 8..剩余 12Thread-0...生产..No 9..剩余 13Thread-0...生产..No 10..剩余 14Thread-0...生产..No 11..剩余 15Thread-0...生产..No 12..剩余 16Thread-0...生产..No 13..剩余 17Thread-0...生产..No 14..剩余 18Thread-0...生产..No 15..剩余 19Thread-0...生产..No 16..剩余 20

我们来看,生产者线程Thread-1首先得到执行权,然后往能容纳20个资源对象的数组中存数据,存到第16个后,被消费者线程Thraed-2获得执行权,它从第一个资源开始取,一直取到第16个,此时数组中的资源已经空了,就通知生产者线程继续生产,生产者会接着上次停留的地方(也就是脚标17)继续生产,到头了就又从头开始生产。循环往复。

总结

Lock和Condition的组合使用,使“锁对象”和锁的“监视器对象”分离,这样既操作灵活,又可以直接唤醒对方线程,提高多线程的效率。

阅读全文
1 0
原创粉丝点击