多线程间通信
第一部分
1、线程通信
|--卖票和存钱,它们各自所要运行的代码都是一致的,如卖票程序,4个窗口分别卖票,4个窗口的程序都是相同的;|--线程通信:现在有一堆资源,A线程在往里面存数据,B这个线程在往外面取数据,如同资源就是煤,有一个车在往里面拉煤,还有一个车在往外面送煤;这一进一出都是同时运行的;它们这两个动作是不一致的,所以它们的代码也就是不一致的;-->所以就要有两个run()方法来存放,那么就要有两个类来进行存放;|--简单一句话:其实就是多个线程在操作同一个资源,但是操作的动作不同;2、线程通信图形
3、代码体现
//描述类 class Res { // 定义两个变量 String name; String sex; boolean flag =false; } //用实现的方法; class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { // 做男和女的切换 int x=0; while(true) { // synchronized(r) // { // if(r.flag) // try // { // r.wait(); // } // catch (Exception e) // { // System.out.println(e.toString()); // } if(x==0) { r.name ="mike"; r.sex ="man"; } else { r.name ="丽丽"; r.sex ="女女女女女"; } // 切换 x=(x+1)%2; // r.flag = true; // r.notify(); // } } } } class Output implements Runnable { // 初始化资源 private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { // synchronized(r) // { // if(!r.flag) // try // { // r.wait(); // } // catch (Exception e) // { // System.out.println(e.toString()); // } System.out.println(r.name+"....."+r.sex); // r.flag = false; // r.notify(); // } } } } class InputOutputDemo { public static void main(String[] args) { // 创建资源 Res r = new Res(); // 实现Runnable接口建立对象Input、Output Input in = new Input(r); Output out = new Output(r); // 创建两个线程; Thread t1 = new Thread(in); Thread t2 = new Thread(out); // 开启线程 t1.start(); t2.start(); } }4、运行结果
从运行结果看,出现了相同的姓名,却有两个性别的产生,或者是相同的性别,出现了不同的姓名;
5、问题产生的原因
|--当Input往里面存mike和man的时候,这是丽丽也存进来了,但是这是刚存完丽丽,这时输出线程获取到了执行权,那么这时输出语句就把丽丽和man给打出去了;也就是说赋值的时候,只赋值了姓名,却没有赋值性别就被输出了;
6、解决问题
解决这个问题还要想到同步的两个前提;
|--是否是两个或者两个以上的线程;|--是否使用的是同一个锁;
第二部分
1、线程通信等待唤醒机制
描述:输入的线程获取到了cpu的执行权,输入了一个mike和man,存完了以后,这是出了同步以后,那么Input和output就再次有可能会抢到cpu的执行权所以这是输入的input就把丽丽和女给输入进去了,前面的mike和man值给覆盖了;当Input获取到了执行权以后,就一直不断的录入,当路途到了某一个时候,它的执行权被输出的给强走了;输出获取到执行权之后,就有可能会把一个值打印多遍;所以造成了一大片的man和一大片的女;
2、问题的解决
在描述的资源上面加上一个标记;当Input在向资源里面存值的时候,要先判断一下,资源里面有没有值,如果标记是false的话,就往里面存入值,存完了以后,输入的还会持有执行权,这时存完后就应该把标记改为true,代表里面有数据了;这是应该让输入线程等待,输入线程应该是存完数据后,输出线程取完数据后,才能醒过来;所以要使用wait();-->也就是冻结了;这是输出语句就只有了cpu的执行权,判断标记为true,就取了一次数据,打印了以后,这是输出还有cpu的执行权就把标记改为了false;这时输出线程在等待之前要把输入线程给叫醒了,然后就等到了;以此类推-->线程通信中的等待唤醒机制。
3、代码体现
//描述类 class Res { // 定义两个变量 String name; String sex; // 定义一个标记 boolean flag =false; } //用实现的方法; class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { // 做男和女的切换 int x=0; while(true) { // 资源是唯一的 synchronized(r) { // 如果flag为true的话,就要等待,而放弃执行资格 if(r.flag)//wait notify 和notifyAll全部用在同步里面; // wait方法要抛异常 try { r.wait();//wait定义在Object里面,因为是任意对象可以调用的方法可以定义在上帝里面; } catch (Exception e) { System.out.println(e.toString()); } if(x==0) { r.name ="mike"; r.sex ="man"; } else { r.name ="丽丽"; r.sex ="女女女女女"; } // 切换 x=(x+1)%2; // 把flag改为true; r.flag = true; // 叫醒输出线程,要使用锁,wait notify 和notifyAll全部用在同步里面; r.notify(); } } } } class Output implements Runnable { // 初始化资源 private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { // 资源是唯一的 synchronized(r) { // flag已经被置为了true,就是false,就不要等待,只要为true就要等待; if(!r.flag) // wait方法要抛异常 try { r.wait(); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(r.name+"....."+r.sex); r.flag = false; // 唤醒输入线程 r.notify(); } } } } class InputOutputDemo { public static void main(String[] args) { // 创建资源 Res r = new Res(); // 实现Runnable接口建立对象Input、Output Input in = new Input(r); Output out = new Output(r); // 创建两个线程; Thread t1 = new Thread(in); Thread t2 = new Thread(out); // 开启线程 t1.start(); t2.start(); } }4、运行的结果
5、对代码进行优化;
/* 对代码继续优化; */ class Res { private String name; private String sex; private boolean flag = false; // 设置姓名和性别,在把函数同步 public synchronized void set(String name,String sex) { // wait notify要放在锁里面; // 非静态同步函数用的锁是this; if(flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } this.name = name; this.sex = sex; flag = true; // 非静态同步函数用的锁是this; this.notify(); } public synchronized void out() { if(!flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(name+"....."+sex); flag = false; this.notify(); } } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x=0; while(true) { if(x==0) r.set("mike","man"); else r.set("丽丽","女女女女女女女女"); x = (x+1)%2; } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { r.out() ; } } } class InputOutputDemo2 { public static void main(String[] args) { // 创建资源; Res r = new Res(); // 创建线程; new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
第三部分
1、生产者和消费者
|--需求想要对生产一个消费一个,并且都是带着标号的;2、代码体现
class Resourse { // 产品名称 private String name; // 编号 private int count =1; // 设置标记 private boolean flag = false; // 同步函数 public synchronized void set(String name) { if(flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } // 在设置名称的时候就带这编号往里面设置,就是你传一个张三,我传一个张三是带着编号的; this.name = name+"---"+count++; // 告诉生产的是什么; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); this.flag = true; this.notifyAll(); } public synchronized void out() { if(!flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"...消费者.................."+this.name); this.flag = false; this.notifyAll(); } } class Producer implements Runnable { // 操作资源 private Resourse res; Producer(Resourse res) { this.res = res; } public void run() { while(true) { res.set("+++商品+++"); } } } class Consumer implements Runnable { // 操作资源 private Resourse res; Consumer(Resourse res) { this.res = res; } public void run() { while(true) { res.out(); } } } class ProducerConsumerDemo { public static void main(String[] args) { // 创建资源 Resourse r = new Resourse(); Producer pro = new Producer(r); Consumer con = new Consumer(r); // 创建线程; Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }3、运行结果
出现了一个现象就是,生产了两个而却消费了一个商品,或者是生产了一个,却消费了两个的现象;4、问题的描述
|--现在出现了4个线程,两个是消费者,两个是生产者;假如现在消费者获取到了cpu的执行权,t1判断标记为false就不需要等待,向里面赋值了;就++了一次,并且将标记置为了true;但是这时又可能还持有执行权,这时的flag为true,判断标记是true,t1就等待了;t1就处于放弃资格,这时t2、t3和t4就有可能会抢到cpu的资源;t|--1(放弃资格)-->t2(放弃资格)-->t2(放弃资格)-->t1(获取资格)-->t3(放弃资格)-->t4(放弃资格)-->t1(获取资格)-->打印了一次
-->notify-->t2(获取资格)(这时t1把本方的唤醒了)-->t1(放弃资格)-->(t2不判断标记,又生产了一个)t2获取资格)-->t2没有判断标记,就导致生产了两个商品,但是消费的时候只消费了t2生产的商品;
|--主要原因就是在t2醒的时候没有判断标记,就向下生产了一个-->那么如何在每次醒的时候都要判断标记?-->用while进行多次的判断;
5、代码体现
class Resourse { // 产品名称 private String name; // 编号 private int count =1; // 设置标记 private boolean flag = false; // 同步函数 public synchronized void set(String name) { while(flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } // 在设置名称的时候就带这编号往里面设置,就是你传一个张三,我传一个张三是带着编号的; this.name = name+"---"+count++; // 告诉生产的是什么; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); this.flag = true; // 全部唤醒所有的线程,它们自己会循环判断标记; this.notifyAll(); } public synchronized void out() { while(!flag) try { this.wait(); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"...消费者.................."+this.name); this.flag = false; // 全部唤醒所有的线程,它们自己会循环判断标记; this.notifyAll(); } } class Producer implements Runnable { // 操作资源 private Resourse res; Producer(Resourse res) { this.res = res; } public void run() { while(true) { res.set("+++商品+++"); } } } class Consumer implements Runnable { // 操作资源 private Resourse res; Consumer(Resourse res) { this.res = res; } public void run() { while(true) { res.out(); } } } class ProducerConsumerDemo { public static void main(String[] args) { // 创建资源 Resourse r = new Resourse(); Producer pro = new Producer(r); Consumer con = new Consumer(r); // 创建线程; Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }6、运行结果
第四部分
1、1.5版本的新特性
import java.util.concurrent.locks.*; class Resourse { private String name; private int count =1; private boolean flag = false; // 建立对象父类 子类 private Lock lock = new ReentrantLock(); // wait和nitifyAll都要所属于锁,但是wait和nitifyAll封装在Condition里面 // 所以Condition要通过Lock来,所以Lock里面有个Condition newCondition()方法; //两个Condition对象; private Condition condition_pro = lock.newCondition();//newContion是Lock中的方法。 private Condition condition_con = lock.newCondition();//newContion是Lock中的方法。 // 没有synchronized同步了 // 没有catch到异常,就要抛出异常,标识一下 public void set(String name)throws InterruptedException { // 锁上,拿到锁 lock.lock();//对象调用 try//不要catch { // 判断标记 while(flag) // 生产者等待 condition_pro.await();//await要抛出异常 this.name = name+"---"+count++;//这个放在等待的后面, System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); flag = true; // 唤醒对方的线程,这就是新特性出现的好处; condition_con.signal();//唤醒消费者者//这时就不要signalAll } // 释放资源; finally { lock.unlock();//释放锁的动作一定要执行。//对象调用 } } public void out()throws InterruptedException { lock.lock();//拿到锁 try { while(!flag) // 消费者等待 condition_con.await(); System.out.println(Thread.currentThread().getName()+"...消费者.................."+this.name); flag = false; // 唤醒对方的线程,这就是新特性出现的好处; condition_pro.signal();//唤醒生产者,这时就不要signalAll } finally { lock.unlock(); } } } class Producer implements Runnable { private Resourse res; Producer(Resourse res) { this.res = res; } public void run() { while(true) { // 这个调用了,也要抛出异常; try { res.set("+++商品+++"); } catch (InterruptedException e) { System.out.println(e.toString()); } } } } class Consumer implements Runnable { private Resourse res; Consumer(Resourse res) { this.res = res; } public void run() { while(true) { try { res.out(); } catch (InterruptedException e) { System.out.println(e.toString()); } } } } class ProducerConsumerDemo2 { public static void main(String[] args) { Resourse r = new Resourse(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }
生产者唤醒消费者,消费者唤醒生产者;
在该例中实现了本方只唤醒对方操作;
2、运行结果
3、面试
问:生产者和消费者有什么替代方案?答:1.5版本以后提供了现实的锁机制,以及现实锁对象上的等待唤醒操作机制,同时把等待唤醒进行了封装,这样一个锁可以对应多个condition;
5、我的总结
|--wait notify 和notifyAll全部用在同步里面,因为要对持有的监视器(锁)进行操作;|--wait notify 和notifyAll定义在Object里面,因为是任意对象可以调用的方法可以定义在上帝里面;|--在线程里面的唤醒机制,如果唤醒的话,是唤醒的第一个等待的线程;按照顺序进行唤醒的;|--notify 往往唤醒的是线程池中的第一个;|--当多个生产者和消费者同时在做事情的事情,必须要写while循环-->循环判断标记,必须要用notifyAll-->唤醒所有的线程;|--1.5版本的新特性
|--Lock替代了synchronized方法和语句的使用,|--Condition替代了 Object 监视器方法的使用。wait notify 和notifyAll被Condition所取代;
|--await替代了wait|--singal替代了notify|--notifyAll替代了notifyAll|--JDK 提供的新方案,这样一个锁可以对应多个
condition
|--lock
|--释放锁的动作一定要执行;|--condition_pro
|--condition_con






57



被折叠的 条评论
为什么被折叠?



