深入理解ReentrantLock和Condition

全文概要

本篇将着手线程并发库,即java.util.concurrent包中的几个重要类。线程并发库是jdk1.5引入的,并发库的引入使得多线程开发更加的灵活多变,除此之外,因为java是面向对象的语言,线程并发库的引入让java多线程编程更加正统。本文主要内容如下:

  1. 介绍LockCondition接口;
  2. 通过使用Lock和Condition来模拟生产者/消费者模型,可以和传统线程实现生产者/消费者模型进行比较阅读。

Lock接口

Lock接口是线程并发库中锁对象的父接口,本文将通过他的实现类ReentrantLock来说明,ReentrantLock是一个可重入的互斥锁,也被称之为“独占锁”,顾名思义,ReentrantLock在同一时刻只能被一个线程拥有,reentrant英文释义就是可重入的,意义就是它可以被单个线程多次获取,相当于synchronized关键字的作用,只不过ReentrantLock是基于对象的。

Condition接口

Condition的作用是对锁进行更加精确的控制,Condition中的await()/signal()/signalAll和Object中wait()/notify()/notifyAll()方法有异曲同工之妙,不同的是Object中的三个方法是基于synchronized关键字,而Condition中的三个方法则需要和Lock联合使用。针对一个Lock对象可以有多个Condition对象,这就是Condition对象更加灵活的原因。

生产者/消费者模型案例

  • 代码案例

仓库模型代码:

package com.tml.javaCore.concurrent.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 多线程 生产者-消费者仓库模型
 * 
 * @author Administrator
 *
 */
public class Repository {
	/*
	 * 仓库的容量
	 */
	private int capacity;
	/*
	 * 仓库的实际容量
	 */
	private int size;
	/*
	 * 独占锁对象,相当于synchronized
	 */
	private Lock lock;
	/*
	 * 仓库满时的条件
	 */
	private Condition fullCondition;
	/*
	 * 仓库空时的条件
	 */
	private Condition emptyCondition;

	public Repository(int capacity) {
		this.capacity = capacity;
		this.size = 0;
		lock = new ReentrantLock();
		fullCondition = lock.newCondition();
		emptyCondition = lock.newCondition();
	}

	/**
	 * <p>
	 * 生产者生产资源
	 * 
	 * @param produceAmount
	 *            生产者预生产量
	 */
	public void produce(int produceAmount) {
		//上锁
		lock.lock();
		try {
			while (produceAmount > 0) {

				while (size >= capacity) {
					System.out.println("has fulled!");
					//仓库已经满了,相当于wait()操作,只有当fullCondition.signalAll()才能继续执行
					fullCondition.await();
				}
				/*
				 * 获取实际的生产量
				 */
				int actAmount = (size + produceAmount) > capacity ? capacity - size : produceAmount;
				size += actAmount;
				System.out.println(
						Thread.currentThread().getName() + ":has + :" + actAmount + ",the actual size is:" + size);
				produceAmount -= actAmount;
				// 通知消费者来消费,相当于notifyAll()操作
				emptyCondition.signalAll();

			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			//释放锁一般放在finally中
			lock.unlock();
		}

	}

	/**
	 * <p>
	 * 消费者消费资源
	 * 
	 * @param consumeAmount
	 *            消费者预消费量
	 */
	public void consume(int consumeAmount) {
		lock.lock();
		try {
			while (consumeAmount > 0) {

				while (size <= 0) {
					System.out.println("empty!");
					emptyCondition.await();
				}
				/*
				 * 获取实际的消费量
				 */
				int actAmount = (size < consumeAmount) ? size : consumeAmount;
				size -= actAmount;
				System.out.println(
						Thread.currentThread().getName() + ":has - :" + actAmount + ",the actual size is:" + size);
				consumeAmount -= actAmount;
				// 通知生产者来生产
				fullCondition.signalAll();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public String toString() {
		return "Repository [capacity=" + capacity + ", size=" + size + "]";
	}

}

测试类代码:

package com.tml.javaCore.concurrent.lock;
/**
 * <p>生产者-消费者
 * @author Administrator
 *
 */
public class TestDemo {
	public static void main(String[] args) {
		//新建一个容量为100的仓库
		Repository repository =new Repository(100);
		
		//模拟生产者,每隔1000毫秒生产23个产品
		new  Thread(new Runnable() {
			public void run() {
				while(true){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					repository.produce(23);
				}
			}
		},"producer1").start();
		
		//模拟消费者1,每隔2500毫秒消费产品25
		new  Thread(new Runnable() {
			public void run() {
				while(true){
					try {
						Thread.sleep(2500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					repository.consume(25);
				}
			}
		},"consumer1").start();
		
		//模拟消费者2,每隔1500毫秒消费产品10
		new  Thread(new Runnable() {
			public void run() {
				while(true){
					try {
						Thread.sleep(1500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					repository.consume(10);
				}
				
			}
		},"consumer2").start();
	}

}
  • 结果输出

下面是某一次的部分结果输出:

    producer1:has + :23,the actual size is:23
    consumer2:has - :10,the actual size is:13
    producer1:has + :23,the actual size is:36
    consumer1:has - :25,the actual size is:11
    producer1:has + :23,the actual size is:34
    consumer2:has - :10,the actual size is:24
    producer1:has + :23,the actual size is:47
    consumer2:has - :10,the actual size is:37
    consumer1:has - :25,the actual size is:12
    producer1:has + :23,the actual size is:35
    producer1:has + :23,the actual size is:58
    consumer2:has - :10,the actual size is:48
    producer1:has + :23,the actual size is:71
    consumer1:has - :25,the actual size is:46
    consumer2:has - :10,the actual size is:36
    producer1:has + :23,the actual size is:59
    producer1:has + :23,the actual size is:82
    consumer2:has - :10,the actual size is:72
    consumer1:has - :25,the actual size is:47
    producer1:has + :23,the actual size is:70
    consumer2:has - :10,the actual size is:60
    producer1:has + :23,the actual size is:83
    producer1:has + :17,the actual size is:100
    has fulled!
    consumer2:has - :10,the actual size is:90
    producer1:has + :6,the actual size is:96
    consumer1:has - :25,the actual size is:71
    producer1:has + :23,the actual size is:94
    consumer2:has - :10,the actual size is:84
    producer1:has + :16,the actual size is:100
    has fulled!

    consumer1:has - :25,the actual size is:75

  • 结果分析
  1. 仓库类中定义了一个锁对象,相当于synchronized的作用,由一个锁对象创建出两个条件对象fullCondition(仓库满)和emptyCondition(仓库空);
  2. 当生产者生产数据的时候,会调用lock.lock()上锁,当生产数量要超过仓库的最大容量的时候,会执行fullCondition.await(),相当于wait()的作用,只有执行fullCondition.signalAll()才能让当前线程继续执行。当生产数量没有超过仓库的最大容量的时候,会调用emptyCondition.signalAll()通知消费者来消费数据;
  3. 当消费者消费数据的时候,会调用lock.lock()上锁,当消费数据量大于仓库的实际容量的时候,会执行emptyCondition.await(),让消费者线程阻塞,只有线程调用emptyCondition.signalAll()才能让当前线程继续执行。当消费数据量小于仓库的实际容量的时候,会调用fullCondition.signalAll()通知生产者产生数据;
  4. 最后,当线程的任务执行完毕后,需要调用lock.unlock()释放对象锁。一般释放对象锁都写在finally中,主逻辑写在try中。这就是使用Lock和Condition来实现生产者/消费者模型的案例,后续还会使用阻塞队列的方式实现。

猜你喜欢

转载自blog.csdn.net/tianmlin1/article/details/79361787