Java并发之条件阻塞Condition的应用代码示例

本文研究的主要是Java并发之条件阻塞Condition的应用示例代码,具体如下。

Condition将Object监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。

1. Condition的基本使用

  由于Condition可以用来替代wait、notify等方法,所以可以对比着之前写过的线程间通信的代码来看,再来看一下原来那个问题:

有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次……如此往返执行50次。

  之前用wait和notify来实现的,现在用Condition来改写一下,代码如下:

public class ConditionCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		new Thread(new Runnable() {
			// 开启一个子线程
			@Override
			          public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub(i);
				}
			}
		}
		).start();
		// main方法主线程
		for (int i = 1; i <= 50; i++) {
			bussiness.main(i);
		}
	}
}
class Business {
	Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	//Condition是在具体的lock之上的
	private Boolean bShouldSub = true;
	public void sub(int i) {
		lock.lock();
		try {
			while (!bShouldSub) {
				try {
					condition.await();
					//用condition来调用await方法
				}
				catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 10; j++) {
				System.out.println("sub thread sequence of " + j
				            + ", loop of " + i);
			}
			bShouldSub = false;
			condition.signal();
			//用condition来发出唤醒信号,唤醒某一个
		}
		finally {
			lock.unlock();
		}
	}
	public void main(int i) {
		lock.lock();
		try {
			while (bShouldSub) {
				try {
					condition.await();
					//用condition来调用await方法
				}
				catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 10; j++) {
				System.out.println("main thread sequence of " + j
				            + ", loop of " + i);
			}
			bShouldSub = true;
			condition.signal();
			//用condition来发出唤醒信号么,唤醒某一个
		}
		finally {
			lock.unlock();
		}
	}
}

从代码来看,Condition的使用时和Lock一起的,没有Lock就没法使用Condition,因为Condition是通过Lock来new出来的,这种用法很简单,只要掌握了synchronized和wait、notify的使用,完全可以掌握Lock和Condition的使用。

2. Condition的拔高

2.1 缓冲区的阻塞队列

  上面使用Lock和Condition来代替synchronized和Object监视器方法实现了两个线程之间的通信,现在再来写个稍微高级点应用:模拟缓冲区的阻塞队列。
什么叫缓冲区呢?举个例子,现在有很多人要发消息,我是中转站,我要帮别人把消息发出去,那么现在我  就需要做两件事,一件事是接收用户发过来的消息,并按顺序放到缓冲区,另一件事是从缓冲区中按顺序取出用户发过来的消息,并发送出去。

  现在把这个实际的问题抽象一下:缓冲区即一个数组,我们可以向数组中写入数据,也可以从数组中把数据取走,我要做的两件事就是开启两个线程,一个存数据,一个取数据。但是问题来了,如果缓冲区满了,说明接收的消息太多了,即发送过来的消息太快了,我另一个线程还来不及发完,导致现在缓冲区没地方放了,那么此时就得阻塞存数据这个线程,让其等待;相反,如果我转发的太快,现在缓冲区所有内容都被我发完了,还没有用户发新的消息来,那么此时就得阻塞取数据这个线程。

  好了,分析完了这个缓冲区的阻塞队列,下面就用Condition技术来实现一下:

class Buffer {
	final Lock lock = new ReentrantLock();
	//定义一个锁
	final Condition notFull = lock.newCondition();
	//定义阻塞队列满了的Condition
	final Condition notEmpty = lock.newCondition();
	//定义阻塞队列空了的Condition
	final Object[] items = new Object[10];
	//为了下面模拟,设置阻塞队列的大小为10,不要设太大
	int putptr, takeptr, count;
	//数组下标,用来标定位置的
	//往队列中存数据
	public void put(Object x) throws InterruptedException {
		lock.lock();
		//上锁
		try {
			while (count == items.length) {
				System.out.println(Thread.currentThread().getName() + " 被阻塞了,暂时无法存数据!");
				notFull.await();
				//如果队列满了,那么阻塞存数据这个线程,等待被唤醒
			}
			//如果没满,按顺序往数组中存
			items[putptr] = x;
			if (++putptr == items.length) //这是到达数组末端的判断,如果到了,再回到始端
			putptr = 0;
			++count;
			//消息数量
			System.out.println(Thread.currentThread().getName() + " 存好了值: " + x);
			notEmpty.signal();
			//好了,现在队列中有数据了,唤醒队列空的那个线程,可以取数据啦
		}
		finally {
			lock.unlock();
			//放锁
		}
	}
	//从队列中取数据
	public Object take() throws InterruptedException {
		lock.lock();
		//上锁
		try {
			while (count == 0) {
				System.out.println(Thread.currentThread().getName() + " 被阻塞了,暂时无法取数据!");
				notEmpty.await();
				//如果队列是空,那么阻塞取数据这个线程,等待被唤醒
			}
			//如果没空,按顺序从数组中取
			Object x = items[takeptr];
			if (++takeptr == items.length) //判断是否到达末端,如果到了,再回到始端
			takeptr = 0;
			--count;
			//消息数量
			System.out.println(Thread.currentThread().getName() + " 取出了值: " + x);
			notFull.signal();
			//好了,现在队列中有位置了,唤醒队列满的那个线程,可以存数据啦
			return x;
		}
		finally {
			lock.unlock();
			//放锁
		}
	}
}

这个程序很经典,我从官方JDK文档中拿出来的,然后加了注释。程序中定义了两个Condition,分别针对两个线程,等待和唤醒分别用不同的Condition来执行,思路很清晰,程序也很健壮。可以考虑一个问题,为啥要用两个Codition呢?之所以这么设计肯定是有原因的,如果用一个Condition,现在假设队列满了,但是有2个线程A和B同时存数据,那么都进入了睡眠,好,现在另一个线程取走一个了,然后唤醒了其中一个线程A,那么A可以存了,存完后,A又唤醒一个线程,如果B被唤醒了,那就出问题了,因为此时队列是满的,B不能存的,B存的话就会覆盖原来还没被取走的值,就因为使用了一个Condition,存和取都用这个Condition来睡眠和唤醒,就乱了套。到这里,就能体会到这个Condition的用武之地了,现在来测试一下上面的阻塞队列的效果:

public class BoundedBuffer {
	public static void main(String[] args) {
		Buffer buffer = new Buffer();
		for (int i = 0; i < 5; i ++) {
			//开启5个线程往缓冲区存数据
			new Thread(new Runnable() {
				@Override
				        public void run() {
					try {
						buffer.put(new Random().nextint(1000));
						//随机存数据
					}
					catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			).start();
		}
		for (int i = 0; i < 10; i ++) {
			//开启10个线程从缓冲区中取数据
			new Thread(new Runnable() {
				@Override
				        public void run() {
					try {
						buffer.take();
						//从缓冲区取数据
					}
					catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			).start();
		}
	}
}

我故意只开启5个线程存数据,10个线程取数据,就是想让它出现取数据被阻塞的情况发生,看运行的结果:

Thread-5 被阻塞了,暂时无法取数据!
Thread-10 被阻塞了,暂时无法取数据!
Thread-1 存好了值: 755
Thread-0 存好了值: 206
Thread-2 存好了值: 741
Thread-3 存好了值: 381
Thread-14 取出了值: 755
Thread-4 存好了值: 783
Thread-6 取出了值: 206
Thread-7 取出了值: 741
Thread-8 取出了值: 381
Thread-9 取出了值: 783
Thread-5 被阻塞了,暂时无法取数据!
Thread-11 被阻塞了,暂时无法取数据!
Thread-12 被阻塞了,暂时无法取数据!
Thread-10 被阻塞了,暂时无法取数据!
Thread-13 被阻塞了,暂时无法取数据!

  从结果中可以看出,线程5和10抢先执行,发现队列中没有,于是就被阻塞了,睡在那了,直到队列中有新的值存入才可以取,但是它们两运气不好,存的数据又被其他线程给抢先取走了,哈哈……可以多运行几次。如果想要看到存数据被阻塞,可以将取数据的线程设置少一点,这里我就不设了。

2.2 两个以上线程之间的唤醒

  还是原来那个题目,现在让三个线程来执行,看一下题目:

有三个线程,子线程1先执行10次,然后子线程2执行10次,然后主线程执行5次,然后再切换到子线程1执行10次,子线程2执行10次,主线程执行5次……如此往返执行50次。

  如过不用Condition,还真不好弄,但是用Condition来做的话,就非常方便了,原理很简单,定义三个Condition,子线程1执行完唤醒子线程2,子线程2执行完唤醒主线程,主线程执行完唤醒子线程1。唤醒机制和上面那个缓冲区道理差不多,下面看看代码吧,很容易理解。

public class ThreeConditionCommunication {
	public static void main(String[] args) {
		Business bussiness = new Business();
		new Thread(new Runnable() {
			// 开启一个子线程
			@Override
			          public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub1(i);
				}
			}
		}
		).start();
		new Thread(new Runnable() {
			// 开启另一个子线程
			@Override
			      public void run() {
				for (int i = 1; i <= 50; i++) {
					bussiness.sub2(i);
				}
			}
		}
		).start();
		// main方法主线程
		for (int i = 1; i <= 50; i++) {
			bussiness.main(i);
		}
	}
	static class Business {
		Lock lock = new ReentrantLock();
		Condition condition1 = lock.newCondition();
		//Condition是在具体的lock之上的
		Condition condition2 = lock.newCondition();
		Condition conditionMain = lock.newCondition();
		private int bShouldSub = 0;
		public void sub1(int i) {
			lock.lock();
			try {
				while (bShouldSub != 0) {
					try {
						condition1.await();
						//用condition来调用await方法
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub1 thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 1;
				condition2.signal();
				//让线程2执行
			}
			finally {
				lock.unlock();
			}
		}
		public void sub2(int i) {
			lock.lock();
			try {
				while (bShouldSub != 1) {
					try {
						condition2.await();
						//用condition来调用await方法
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub2 thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 2;
				conditionMain.signal();
				//让主线程执行
			}
			finally {
				lock.unlock();
			}
		}
		public void main(int i) {
			lock.lock();
			try {
				while (bShouldSub != 2) {
					try {
						conditionMain.await();
						//用condition来调用await方法
					}
					catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 5; j++) {
					System.out.println("main thread sequence of " + j
					              + ", loop of " + i);
				}
				bShouldSub = 0;
				condition1.signal();
				//让线程1执行
			}
			finally {
				lock.unlock();
			}
		}
	}
}

代码看似有点长,但是是假象,逻辑非常简单。关于线程中的Condition技术就总结这么多吧。

总结

以上就是本文关于Java并发之条件阻塞Condition的应用代码示例的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • Java使用Condition控制线程通信的方法实例详解

    本文实例讲述了Java使用Condition控制线程通信的方法.分享给大家供大家参考,具体如下: 一 点睛 当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象.却无法继续执行的线程释放Lock对象,Condtion对象也可以唤醒其他处于等待的线程. Condition 将同步监视锁方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多

  • Java并发编程之Condition源码分析(推荐)

    Condition介绍 上篇文章讲了ReentrantLock的加锁和释放锁的使用,这篇文章是对ReentrantLock的补充.ReentrantLock#newCondition()可以创建Condition,在ReentrantLock加锁过程中可以利用Condition阻塞当前线程并临时释放锁,待另外线程获取到锁并在逻辑后通知阻塞线程"激活".Condition常用在基于异步通信的同步机制实现中,比如dubbo中的请求和获取应答结果的实现. 常用方法 Condition中主要的

  • java 多线程-线程通信实例讲解

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 通过共享对象通信 忙等待 wait(),notify()和 notifyAll() 丢失的信号 假唤醒 多线程等待相同信号 不要对常量字符串或全局对象调用 wait() 通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值.线程 A 在一个同步块里设置 boolean 型成员变量 hasDataToProcess 为 true,线程 B 也在同步块里读取 hasDataToProc

  • 举例讲解Java中Piped管道输入输出流的线程通信控制

    PipedOutputStream和PipedInputStream 在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流. 它们的作用是让多线程可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用. 使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的Pip

  • 深入理解java线程通信

    前言 开发中不免会遇到需要所有子线程执行完毕通知主线程处理某些逻辑的场景. 或者是线程 A 在执行到某个条件通知线程 B 执行某个操作. 可以通过以下几种方式实现: 等待通知机制 等待通知模式是 Java 中比较经典的线程通信方式. 两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯. 如两个线程交替打印奇偶数: public class TwoThreadWaitNotify { private int start = 1; private boolean

  • Java编程中实现Condition控制线程通信

    java中控制线程通信的方法 1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyAll()控制线程通信.不灵活. 2.利用Condition控制线程通信,灵活. 3.利用管道pipe进行线程通信,不推荐 4.利用BlockingQueue控制线程通信 本文就讲解利用Condition控制线程通信,非常灵活的方式. Condition类是用来保持Lock对象的协调调用. 对Lock不了解的可以参考:Java线程同步Lock同步锁代码示例

  • Java多线程编程中使用Condition类操作锁的方法详解

    Condition的作用是对锁进行更精确的控制.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法.不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的:而Condition是需要与"互斥

  • Java多线程中ReentrantLock与Condition详解

    一.ReentrantLock类 1.1什么是reentrantlock java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现.这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外,它还提供了在激烈争用情况下更

  • Java使用阻塞队列控制线程通信的方法实例详解

    本文实例讲述了Java使用阻塞队列控制线程通信的方法.分享给大家供大家参考,具体如下: 一 点睛 阻塞队列主要用在生产者/消费者的场景,下面这幅图展示了一个线程生产.一个线程消费的场景: 负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值.队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费.同理,负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入. BlockingQueue的核心方法:

  • Java线程通信详解

    线程通信用来保证线程协调运行,一般在做线程同步的时候才需要考虑线程通信的问题. 1.传统的线程通信 通常利用Objeclt类提供的三个方法: wait() 导致当前线程等待,并释放该同步监视器的锁定,直到其它线程调用该同步监视器的notify()或者notifyAll()方法唤醒线程. notify(),唤醒在此同步监视器上等待的线程,如果有多个会任意选择一个唤醒 notifyAll() 唤醒在此同步监视器上等待的所有线程,这些线程通过调度竞争资源后,某个线程获取此同步监视器的锁,然后得以运行.

随机推荐