玩转并发-线程间通信

生产者与消费者模式

讲到线程间通信,很难不提及生产者与消费者模式。我先写个简单的生产者与消费者的栗子。
在此之前,先ps下Objecy的wait()和notifly()
JDK文档:

void notify() 
//唤醒在此对象监视器上等待的某个线程
//Wakes up a single thread that is waiting on this object’s monitor. 

void wait( )
//Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object
//导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

wait()和notify()不属于Thread类,属于Object类。因为每个对象都有锁,所以操作锁的方法是基础,写入Object类也不奇怪了。

public class ProducerConsumer {
	
	static final Object Mutex=new Object();
	private static  int i=0;
	private boolean isProduce=false;
	
	public void Produce() {
		synchronized (Mutex) {
			if(isProduce) {
				//如果已生产
				try {
					Mutex.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else {
				//未生产则生产
				System.out.println("Producer:"+i);
				i++;
				isProduce=true;
				Mutex.notify();
			}
		}
	}
	
	public void Consumer() {
		synchronized (Mutex) {
			if(isProduce) {
				//已生产则消费
				System.out.println("Consumer"+i);
				isProduce=false;
				Mutex.notify();
			}else {
				//未生产则等待
				try {
					Mutex.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}	

public static void main(String[] args) throws InterruptedException {
			final ProducerConsumer producerConsumer = new ProducerConsumer();
			Thread thread1 = new Thread() {
				@Override
				public void run() {
						producerConsumer.Produce();	
				}
			};
			Thread thread2 = new Thread() {
				@Override
				public void run() {
						producerConsumer.Consumer();	
				}
			};
			thread1.start();
			thread2.start();	
	}

wait和notify的注意事项

  1. 线程执行了某个对象的wait的方法之后,会加入与之对应的wait set中。每一个monitor都有一个与之关联的wait set
  2. 必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提是必须持有同步方法的monitor所有权,否则抛出异常。
线程休息室 wait set

wait set的实现并没有统一的规定,不同JDK的实现都不同。
在这里插入图片描述在这里插入图片描述

  1. 所有的对象都都会有一个waitset,用来存放该对象wait()方法之后进入blocked状态的线程
  2. 线程从waitset被唤醒的顺序不一定是FIFO
  3. 进入waitset的线程被唤醒后仍然需要去抢得锁的控制权,才能进入同步方法。由于每个线程有自己得程序计数器,线程到RUNNING状态后会切换到原来自己BLOCKED状态的位置往下执行。

上面这个生产者和消费者模式的栗子,在单Producer和单Consumer下可以正常工作,但是不适合在多Producer和多Consumer下运行。
改造下代码,引入notifyall方法

void notifyAll() 
//Wakes up all threads that are waiting on this object’s monitor. 
//唤醒在此对象监视器上等待的所有线程
public void Produce() {
		synchronized (Mutex) {
			//这里用while而不用if
			//notiflAll()唤醒全部线程,如果只是if的话,全部线程都会执行下面的生产,造成逻辑混乱
			while(isProduce) {
				//如果已生产
				try {
					Mutex.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			    i++;
				System.out.println("Producer:"+i);
				isProduce=true;
				Mutex.notifyAll();
		}
	}
	
	public void Consumer() {
		synchronized (Mutex) {
			while(!isProduce) {
				try {
					Mutex.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("Consumer:"+i);
			isProduce=false;
			Mutex.notifyAll();
			}
	}

wait()和sleep()的本质区别

  1. wait方法和sleep方法都可以使当前线程进入阻塞状态
  2. 均是可中断方法,被中断后都会收到中断异常
  3. wait是Object的方法,sleep是Thread特有的方法
  4. wait方法的执行必须在同步方法中,而sleep不需要
  5. 线程在同步方法执行sleep,并不会释放monito锁;而wait方法则会执行monitor的锁
  6. sleep方法短暂休眠后会主动退出阻塞,而wait方法(没有指定执行时间)则需要其中线程中断后才能退出阻塞

自定义显氏锁BooleanLock

synchronized关键字的缺陷
  1. 无法控制阻塞时长
  2. 阻塞不可被中断,虽然可以设置线程的interrupt标识,但是synchronized阻塞不像sleep和wait能够捕获得到中断信号。
自定义Lock

接口

public interface Lock {
	
	public class TimeOutException extends Exception{
		private static final long serialVersionUID = 1L;
		public TimeOutException(String message) {
			super(message);
		}
	}
	
	public void lock() throws InterruptedException;
	public void lock(long mills) throws InterruptedException,TimeOutException;
	public void unlock();
	public List<Thread> getThreads();
	public int getThreadsSize();
}

实现类

public class BooleanLock implements Lock{
	
	//false代表没有线程获取
	//true代表有线程获取
	private boolean lockFlag;
	private Thread currentThread;
	
	public BooleanLock() {
		//默认创建lockFlag为false
		this.lockFlag=false;
	}
	
	//阻塞线程List
	private List<Thread> listThread=new ArrayList<Thread>();
	
	@Override
	public synchronized void lock() throws InterruptedException {
		//当锁已被其他线程占用
		while(lockFlag) {
			//将获取不到锁的线程放进阻塞线程list
			listThread.add(Thread.currentThread());
			this.wait();
		}
		//成功获取到锁
		//从阻塞线程从移出
		if(listThread.contains(Thread.currentThread())) {
			listThread.remove(Thread.currentThread());
		}
		this.lockFlag=true;
		//获取当前线程,方便之后的操作
		//只有lock的线程,才能进行接下来的unlock
		this.currentThread=Thread.currentThread();
		System.out.println(Thread.currentThread().getName()+"获取monitor");
	}

	@Override
	public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
		//传入参数判断
		if(mills<=0) {
			lock();
		}
		long hasRemain=mills;
		//截止时间
		long endTime=System.currentTimeMillis()+mills;
		while(lockFlag) {
			//超出等待时间,抛出TimeOutException异常
			if(hasRemain<=0)
				throw new TimeOutException("Time out");
			listThread.add(Thread.currentThread());
			this.wait(mills);
			hasRemain=System.currentTimeMillis()-endTime;
		}
		this.lockFlag=true;
		this.currentThread=Thread.currentThread();
		System.out.println(Thread.currentThread().getName()+"获取monitor");
		
	}

	@Override
	public synchronized void unlock() {
		//只有lock的线程才能执行unlock
		if(currentThread==Thread.currentThread()) {
			this.lockFlag=false;
			System.out.println(Thread.currentThread().getName()+"释放该monitor");
			//唤醒wait此锁的全部线程
			this.notifyAll();
		}	
	}

	@Override
	public List<Thread> getThreads() {
		//禁止修改
		return Collections.unmodifiableList(listThread);
	}
}

测试类

final BooleanLock booleanlock=new BooleanLock();
		Thread thread1 = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						booleanlock.lock(10);
						work();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (TimeOutException e) {
						e.printStackTrace();
					}
					finally {
						booleanlock.unlock();
					}	
				}
			}
		};
		Thread thread2 = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						booleanlock.lock(10);
						work();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (TimeOutException e) {
						e.printStackTrace();
					}
					finally {
						booleanlock.unlock();
					}	
				}
				
			}
		};
		Thread thread3 = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						booleanlock.lock(10);
						work();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (TimeOutException e) {
						e.printStackTrace();
					}
					finally {
						booleanlock.unlock();
					}	
				}		}
		};
		thread1.start();
		thread2.start();
		thread3.start();
	}
	public static void work() throws InterruptedException {
			System.out.println(Thread.currentThread().getName()+"is working");
	}

打印结果:
Thread-2is working
Thread-2释放该monitor
Thread-2获取monitor
Thread-2is working
Thread-2释放该monitor

com.Reyco.MyThread.Lock$TimeOutException: Time out
at com.Reyco.MyThread.BooleanLock.lock(BooleanLock.java:44)
at com.Reyco.MyThread.MyTest$1.run(MyTest.java:13)

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值