问题描述:桌上有一盘子,桌上有一个空盘,允许存放一只水果,爸爸可向盘内放苹果,妈妈可向盘内放桔子,儿子专等吃盘内的桔子,女儿专等吃盘中的苹果。
package Fruit;
import java.util.concurrent.locks.*;// java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块
/*
问题描述:桌上有一盘子,桌上有一个空盘,允许存放一只水果,爸爸可向盘内放苹果,妈妈可向盘内放桔子,儿子专等吃盘内的桔子,女儿专等吃盘中的苹果。
关键技术:锁、
Object类是Java中所有类的父类,是Java底层级别的,Condition是语言级别的,具有更高的可控制性和扩展性。
为什么要使用?Condition?利用Object的方式实际上是指在对象Object对象监视器上只能拥有一个同步队列和一个等待队列; 并发包中的Lock拥有一个同步队列和多个等待队列。
Condition能够支持不响应中断,而通过使用Object方式不支持
Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个
Condition能够支持超时时间的设置,而Object不支持
Condition相比较而言,强大的地方在于它能够精确的控制多线程的休眠与唤醒(注意是唤醒,唤醒并不表示该线程一定能够得到资源锁)
Object的wait和notify,Condition使用await和signal
创建一个Condition对象是通过lock.newCondition()
void await() throws InterruptedException//当前线程进入等待状态,如果在等待状态中被中断会抛出被中断异常
当前线程调用condition.await()方法后,会使得当前线程释放lock然后加入到等待队列中
直至被signal/signalAll后会使得当前线程从等待队列中移至到同步队列中去, 直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。
void signal()//唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
如果不加上Thread.sleep()来让线程睡眠,结果就像是单线程一样,生产者填满队列,消费者清空队列。
这个线程在释放这个锁之后会加入这个锁的竞争中
修改sleep的睡眠时间,设置不同的休眠时间,可以观察到生产者与消费者也不会出现交替进行,还是随机的。
那么为什么要用Condition实现对确定线程的唤醒操作呢?唤醒了又不一定得到锁,这个需要使用到await()来让当前线程必须等到其他线程来唤醒才能控制生产者与消费者的交替执行。
Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
如果有多个消费者用Condition实现就非常简单了,如果使用Object监视器类也可以实现但是相对复杂,编程过程中容易出现死锁
*/
public class EatFruit {
public static void main(String[] args) {
Action r = new Action();
Father father = new Father(r);
Mother mother = new Mother(r);
Son son = new Son(r);
Daughter daughter = new Daughter(r);
//thread每个线程都独立,不共享资源 ,如果要共享需要加上同步条件synchronized
//在程序开发中只要是多线程肯定永远以实现Runnable接口为主。
//实现Runnable接口相比继承Thread类有如下好处:
//1、避免继承的局限,一个类可以继承多个接口。
//2、适合于资源的共享。
Thread f = new Thread(father);
Thread m = new Thread(mother);
Thread s = new Thread(son);
Thread d = new Thread(daughter);
f.start();
m.start();
s.start();
d.start();
}
}
class Action //行为类,放水果和吃水果,完成初始化。
{
//由于水果数量是在整个程序中贯穿的,应该使用全局变量static int
private static int apple_number = 0,orange_number= 0,fruit_number = 0; // 苹果数量,橘子数量,盘子中水果数量
ReentrantLock lock = new ReentrantLock(); // 锁对象
// 同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块
// synchronized和ReentrantLock作用类似,ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。
// synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联,多线程情况下会导致同时运行
// ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场
// 创建一个Condition对象是通过lock.newCondition()
Condition father_lock = lock.newCondition();
Condition mother_lock = lock.newCondition();
Condition parent_lock = lock.newCondition();
Condition son_lock = lock.newCondition();
Condition daughter_lock = lock.newCondition();
private String name;
// 放水果
void put(String name) {
// https://www.cnblogs.com/gemine/p/9039012.html
lock.lock();//调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
try {
Thread.sleep(1000);//如果不加上Thread.sleep()来让线程睡眠,结果就像是单线程一样,生产者填满队列,消费者清空队列。
} catch (Exception e) {
e.printStackTrace();
}
try {
while (fruit_number == 1) // 监测盘子是否有水果,如果已经有水果了,妈妈和妈妈都不能向里面放入水果,同构await实现当前线程进入等待状态
{
try {
// 测试多线程状态下线程情况。
// System.out.print("盘中已有水果,禁止父母放入\n");
parent_lock.await();
//一开始分别设计了爸爸和妈妈的lock,但是发现没有意义,必须掌握的是await和signal间的呼应关系
// System.out.print("父母线程进入等待状态\n");
} catch (Exception e) {
e.printStackTrace();
}
}
if (name == "爸爸")
{
apple_number = 1;
fruit_number = 1;
this.name = name; // 不加名字会变成null
System.out.println(this.name + "放苹果");
daughter_lock.signal();//已经验证爸爸放入的水果,那么只有女儿能吃苹果,唤醒女儿
} else if (name == "妈妈") // 妈妈放了桔子
{
orange_number= 1;
fruit_number = 1;
this.name = name;
System.out.println(this.name + "放桔子");
son_lock.signal();//已经验证妈妈放入的水果,那么只有儿子能吃桔子,唤醒儿子
}
} finally {
lock.unlock();
}
}
// 吃水果
void eat(String name) {
lock.lock();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
try {
if (name == "儿子") // 儿子
{
if (orange_number == 0) {
try {
// System.out.print("盘中没有桔子,儿子线程进入等待状态\n");
son_lock.await();
} catch (Exception e) {
e.printStackTrace();
}
}
orange_number = 0;
fruit_number = 0;
this.name = name;
System.out.println(this.name + "吃桔子");
parent_lock.signal();
}
else if (name == "女儿") // 女儿
{
while (apple_number== 0) {
try {
// System.out.print("盘中没有苹果,女儿线程进入等待状态\n");
daughter_lock.await();
} catch (Exception e) {
e.printStackTrace();
}
}
apple_number= 0;
fruit_number = 0;
this.name = name;
System.out.println(this.name + "吃苹果");
parent_lock.signal();
}
} finally {
lock.unlock();
}
}
}
//线程的资源和 Thread 实例捆绑在一起,所以不同的线程的资源不会进行共享。
//线程资源与 Runable 实例捆绑在一起,Thread 只是作为一个代理类,所以资源可以进行共享。
class Father implements Runnable {
private Action r;
Father(Action r) {
this.r = r;
}
public void run() {
while (true) {//while语句可循环,if语句只会有一次操作
// 为什么要使用while而不是多次调用start
// 线程的生命周期中,线程的状态由NEW ----> RUNABLE只会发生一次,
// 因此,一个线程只能调用start()方法一次,多次启动一个线程是非法的。
// 特别是当线程已经结束执行后,不能再重新启动。
r.put("爸爸");
}
}
// private void put(String string) {
// // TODO Auto-generated method stub
//
// }
}
class Mother implements Runnable {
private Action r;
Mother(Action r) {
this.r = r;
}
public void run() {
while (true) {
r.put("妈妈");
}
}
}
class Son implements Runnable {
private Action r;
Son(Action r) {
this.r = r;
}
public void run() {
while (true) {
r.eat("儿子");
}
}
}
class Daughter implements Runnable {
private Action r;
Daughter(Action r) {
this.r = r;
}
public void run() {
while (true) {
r.eat("女儿");
}
}
}
参考:https://blog.csdn.net/hkdg1651648029/article/details/76737959?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242