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

前言

前面使用的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..剩余 1
Thread-1...生产..No 2..剩余 2
Thread-1...生产..No 3..剩余 3
Thread-1...生产..No 4..剩余 4
Thread-1...生产..No 5..剩余 5
Thread-1...生产..No 6..剩余 6
Thread-1...生产..No 7..剩余 7
Thread-1...生产..No 8..剩余 8
Thread-1...生产..No 9..剩余 9
Thread-1...生产..No 10..剩余 10
Thread-1...生产..No 11..剩余 11
Thread-1...生产..No 12..剩余 12
Thread-1...生产..No 13..剩余 13
Thread-1...生产..No 14..剩余 14
Thread-1...生产..No 15..剩余 15
Thread-1...生产..No 16..剩余 16
Thread-2..消费..No 1..剩余 15
Thread-2..消费..No 2..剩余 14
Thread-2..消费..No 3..剩余 13
Thread-2..消费..No 4..剩余 12
Thread-2..消费..No 5..剩余 11
Thread-2..消费..No 6..剩余 10
Thread-2..消费..No 7..剩余 9
Thread-2..消费..No 8..剩余 8
Thread-2..消费..No 9..剩余 7
Thread-2..消费..No 10..剩余 6
Thread-2..消费..No 11..剩余 5
Thread-2..消费..No 12..剩余 4
Thread-2..消费..No 13..剩余 3
Thread-2..消费..No 14..剩余 2
Thread-2..消费..No 15..剩余 1
Thread-2..消费..No 16..剩余 0
Thread-0...生产..No 17..剩余 1
Thread-0...生产..No 18..剩余 2
Thread-0...生产..No 19..剩余 3
Thread-0...生产..No 0..剩余 4
Thread-0...生产..No 1..剩余 5
Thread-0...生产..No 2..剩余 6
Thread-0...生产..No 3..剩余 7
Thread-0...生产..No 4..剩余 8
Thread-0...生产..No 5..剩余 9
Thread-0...生产..No 6..剩余 10
Thread-0...生产..No 7..剩余 11
Thread-0...生产..No 8..剩余 12
Thread-0...生产..No 9..剩余 13
Thread-0...生产..No 10..剩余 14
Thread-0...生产..No 11..剩余 15
Thread-0...生产..No 12..剩余 16
Thread-0...生产..No 13..剩余 17
Thread-0...生产..No 14..剩余 18
Thread-0...生产..No 15..剩余 19
Thread-0...生产..No 16..剩余 20

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

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值