Lock&Condition实现线程同步通信

一,Lock

      Lock比传统的线程模型中的synchronized方式更加面向对象,因为“锁”本身就是一个对象。

两个线程执行的代码要实现同步互斥的效果,他们必须用同一个Lock对象。


      读写锁:(1)读锁:多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,我们只需要代码中用对相应的锁即可。如果只读数据,那么可以很多人(线程)同时读,但是不能同时写,此时就加读锁。如果代码需要修改数据,此时只能一个人(一个线程)写,此时不能同时读,那么就加写锁。

       总之,读时,上读锁;写时,上写锁。

 

二,Condition


      Condition  Object 监视器方法(waitnotifynotifyAll)分解成截然不同的对象

以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 setwait-set)。

其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方

法的使用


       条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能

为 true的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信

息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一

个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的

那样。

       Condition 实例实质上被绑定到一个锁上。要为特定 Lock实例获得 Condition 实例,

使用newCondition()方法。

 

1,一个锁上一个Condition的简单使用 Demo


       两个线程(主线程、子线程):子线程循环4次,接着主线程循环5,接着又回到子线程循环4

次,接着再回到主线程又循环5,如此循环3次,请写出程序。

 

代码如下:使用一个Condition对象完成两线程之间的通信

package com.tgb.thread13;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Condition实现线程同步通信——简单Demo
 * @author hanxuemin
 *
 */
public class ConditionCommunication {

	public static void main(String[] args) {
		
		final Business business = new Business();
		/**
		 * 创建一个子线程
		 */
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					/**
					 * 循环3次,调用sub()方法
					 */
						for(int i=1;i<=3;i++){
							business.sub(i);
						}
						
					}
				}
		).start();
		
		for(int i=1;i<=3;i++){
			business.main(i);
		}
		
	}

	static class Business {
			Lock lock = new ReentrantLock(); //创建锁对象
			Condition condition = lock.newCondition(); //创建condition对象
		  private boolean bShouldSub = true;
		  /**
		   * 子线程调用的方法
		   * @param i
		   */
		  public  void sub(int i){
			  lock.lock(); //加锁
			  try{
				  //如果标识变量bShouldSub=false,则阻塞线程
				  while(!bShouldSub){
					  try {
						condition.await(); //阻塞该线程,等待。。。
					} catch (Exception e) {
						e.printStackTrace();
					}
				  }
				  /**
				   * 循环4次,打印
				   */
					for(int j=1;j<=4;j++){
						System.out.println("sub thread sequence of " + j + ",loop of " + i);
					}
				  bShouldSub = false; //给标识变量bShouldSub=true
				  condition.signal(); //唤醒其他线程
			  }finally{
				  lock.unlock(); //释放锁
			  }
		  }
		  /**
		   * 主线程方法
		   * @param i
		   */
		  public  void main(int i){
			  lock.lock(); //加锁
			  try{
				//如果标识变量bShouldSub=true,则阻塞线程
				 while(bShouldSub){
				  		try {
							condition.await(); //阻塞该线程,等待。。。
						} catch (Exception e) {
							e.printStackTrace();
						}
				  	}
				 /**
				   * 循环5次,打印
				   */
					for(int j=1;j<=5;j++){
						System.out.println("main thread sequence of " + j + ",loop of " + i);
					}
					bShouldSub = true;
					condition.signal(); //唤醒其他线程
		  }finally{
			  lock.unlock();//释放锁
		  }
	  }
	
	}
}


       Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,但传统

线程技术实现的互斥只能一个线程单独执行,不能实现线程间通信(也就是说这个线程执行完了通

知另一个线程来执行);而Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做

事,小弟说我先歇着并通知大哥,大哥就开始做事。

       Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以

便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其

中,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使

用。

       Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用

其 newCondition() 方法。

2,在java5中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调
用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。

 

可阻塞队列,代码:

package com.tgb.thread13;

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

/**
 * 两个Condition实现的可阻塞队列
 * @author hanxuemin
 *
 */
public class BoundedBuffer {

	final Lock lock = new ReentrantLock(); //创建锁
	final Condition notFullCondition = lock.newCondition(); //写线程条件
	final Condition notEmptyCondition = lock.newCondition(); //读线程条件
	
	//创建大小为100的数组,相当于阻塞队列(缓存队列)
	final Object[] items = new Object[100];
	int putptr; //写索引
	int takeptr; //读索引
	int count; //队列中存在的数据个数
	
	/**
	 * 往数组items中存放数据的方法
	 * @param x 存放的数据
	 * @throws InterruptedException
	 */
	public void put(Object x) throws InterruptedException{
		lock.lock();
		try {
			while (count == items.length) { //如果队列已满,则阻塞写线程
				notFullCondition.await(); //写线程阻塞
			}
			/**
			 * 以下四步为一大步
			 *   1,存值
			 *   2,判断写索引,如果是队列最后一个位置,则置为0
			 *   3,数据个数++
			 *   4,唤醒读线程		 
			 * */
			items[putptr] = x; //数组items中存入数据x
			if(++putptr == items.length) putptr = 0; //如果写索引写到队列的最后一个位置了,那么置为0
			++count; //同时数据个数++
			notEmptyCondition.signal(); //唤醒读线程
		}finally{
		 lock.unlock();	//释放锁
		}
	}
	
	/**
	 * 从数组items中取数据
	 * @return items中取出的数据
	 * @throws InterruptedException
	 */
	public Object take() throws InterruptedException{
		lock.lock();
		try {
			while (count == 0) { //如果队列为空
				notEmptyCondition.await();//阻塞读线程
			}
			/**
			 * 以下四步为一大步
			 *   1,取值
			 *   2,判断读索引,如果是队列最后一个位置,则置为0
			 *   3,数据个数--			
			 *   4,唤醒写线程 
			 * */
			Object x = items[takeptr]; //取值
			if(++takeptr == items.length) takeptr = 0; //如果读索引读到队列的最后一个位置,则置为0
			--count; //同时数据个数--
			notFullCondition.signal();//唤醒写线程
			return x;
		}finally{
			lock.unlock(); //释放锁
		}
		
	}
}

       这是一个处于多线程工作环境下的缓存区,缓存区提供了两个方法,puttakeput是存数

据,take是取数据,内部有个缓存队列,具体变量和方法说明见代码,这个缓存区类实现的功能:

有多个线程往里面存数据和从里面取数据,其缓存队列(先进先出后进后出)能缓存的最大数值

100,多个线程间是互斥的,当缓存队列中存储的值达到100时,将写线程阻塞,并唤醒读线程

当缓存队列中存储的值为0时,将读线程阻塞,并唤醒写线程,下面分析一下代码的执行过程:

   1.一个写线程执行,调用put方法;

   2.判断count是否为100,显然没有100

   3.继续执行,存入值;

   4.判断当前写入的索引位置++后,是否和100相等,相等将写入索引值变为0,并将count+1

   5.仅唤醒读线程阻塞队列中的一个;

   6.一个读线程执行,调用take方法;

   7. ……

   8.仅唤醒写线程阻塞队列中的一个。

       这就是多个Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤

醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一

Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程

了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这

时又去唤醒,这样就浪费了很多时间。

 

  3,另一个一个锁有多个条件的Demo


      子线程二循环10次,子线程三循环5次,接着主线程循环15,接着又回到子线程二循环10次,

子线程三循环5次,接着再回到主线程又循环15,如此循环20次,请写出程序。

 

——子线程二执行完了,唤醒子线程三;子线程三执行完了,唤醒主线程;主线程执行完了,唤醒

子线程二。并且子线程二只能唤醒子线程三,子线程三只能唤醒主线程,主线程只能唤醒子线二。

package com.tgb.thread13;

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

/**
 * Lock&Condition三个线程彼此同步通信
 * 
 * @author hanxuemin
 *
 */
public class ThreeConditionCommunication {

	public static void main(String[] args) {

		final Business business = new Business();
		/**
		 * 出主线程外,再创建两个线程
		 */
		// 创建一个子线程2,循环次,调用sub2()方法
		new Thread(new Runnable() {

			@Override
			public void run() {

				/**
				 * 循环20次调用sub2()方法
				 */
				for (int i = 1; i <= 20; i++) {
					business.sub2(i); // 调用sub2()方法
				}

			}
		}).start();
		// 再创建一个子线程3,循环50次,调用sub3()方法
		new Thread(new Runnable() {

			@Override
			public void run() {
				/**
				 * 循环20次调用sub3()方法
				 */
				for (int i = 1; i <= 20; i++) {
					business.sub3(i); // 调用sub3()方法
				}

			}
		}).start();
		/**
		 * 主线程 循环20次,调用main()方法
		 */
		for (int i = 1; i <= 20; i++) {
			business.main(i);
		}

	}

	/**
	 * 
	 * @author hanxuemin
	 *
	 */
	static class Business {
		Lock lock = new ReentrantLock();
		Condition condition1 = lock.newCondition(); // 线程条件1
		Condition condition2 = lock.newCondition(); // 线程条件2
		Condition condition3 = lock.newCondition(); // 线程条件3
		private int shouldSub = 1; // 标识符变量,初始值为1;当值为1时主线程执行,子线程2和子线程3等待;
									// 当值为2时子线程2执行,其他线程等待;当值为3时子线程3执行,其他线程等待

		/**
		 * 子线程2调用的方法
		 * 
		 * @param i
		 */
		public void sub2(int i) {
			lock.lock(); // 加锁
			try {
				while (shouldSub != 2) { // 当标识符变量!=2时,子线程2阻塞,处于等待状态
					try {
						condition2.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				/**
				 * 循环10次,打印当前线程信息(调用此方法的为子线程2,即打印子线程2的信息)
				 */
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub2 thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 3; // 给标识符变量赋值为3
				condition3.signal(); // 唤醒子线程3
			} finally {
				lock.unlock(); // 释放锁
			}
		}

		/**
		 * 子线程3调用的方法
		 * 
		 * @param i
		 */
		public void sub3(int i) {
			lock.lock(); // 加锁
			try {
				while (shouldSub != 3) { // 当标识符变量!=3时,子线程3阻塞,处于等待状态
					try {
						condition3.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				/**
				 * 循环5次,打印当前线程信息(调用此方法的为子线程3,即打印子线程3的信息)
				 */
				for (int j = 1; j <= 5; j++) {
					System.out.println("sub3 thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 1; // 给标识符变量赋值为1
				condition1.signal(); // 唤醒主线程
			} finally {
				lock.unlock(); // 释放锁
			}
		}

		/**
		 * 主线程调用的方法
		 * 
		 * @param i
		 */
		public void main(int i) {
			lock.lock(); // 加锁
			try {
				while (shouldSub != 1) { // 当标识符变量!=1时,子线程1阻塞,处于等待状态
					try {
						condition1.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				/**
				 * 循环15次,打印当前线程信息(调用此方法的为主线程,即打印主线程的信息)
				 */
				for (int j = 1; j <= 15; j++) {
					System.out.println("main thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 2; // 给标识符变量赋值为2
				condition2.signal(); // 唤醒子线程2
			} finally {
				lock.unlock(); // 释放锁
			}
		}

	}
}


总结:

1 Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其newCondition() 方法。

2Condition解决了线程间的通信问题。

3一个锁可以有多个Condition,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signal()方法,又可以唤醒该条件下的等待的其他线程。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值