Day12多线程:
1.线程间通信-示例代码
2.线程间通信-解决安全问题
3.线程间通信-等待唤醒机制
4.线程间通信-代码优化
5.线程间通信-生产者消费者
1.线程间通信-示例代码
线程间通信:
其实就是多个线程在操作同一个资源,
但操作的动作不同。
示例代码:
1 class Res 2 2 { 3 3 String name; 4 4 String sex; 5 5 6 6 } 7 7 class Input implements Runnable 8 8 { 9 9 int x=0;10 10 private Res r;11 11 public Input(Res r)12 12 {13 13 this.r=r;14 14 15 15 }16 16 public void run()17 17 {18 18 while(true)19 19 {20 20 if(x==0)21 21 {22 22 r.name="Mike";23 23 r.sex="man";24 24 }25 25 else26 26 {27 27 r.name="黎黎";28 28 r.sex="女女女";29 29 30 30 }31 31 x=(x+1)%2;32 32 33 33 }34 34 35 35 36 36 }37 37 }38 38 class Output implements Runnable39 39 {40 40 private Res r;41 41 public Output(Res r)42 42 {43 43 this.r=r;44 44 }45 45 public void run()46 46 {47 47 while(true)48 48 {49 49 System.out.println(r.name+"---"+r.sex);50 50 51 51 }52 52 53 53 }54 54 }55 55 public class InputOutputDemo56 56 {57 57 public static void main(String[] args)58 58 {59 59 Res r=new Res();60 60 Output out=new Output(r);61 61 Input in=new Input(r);62 62 63 63 Thread t1=new Thread(in);64 64 Thread t2=new Thread(out);65 65 66 66 t1.start();67 67 t2.start();68 68 }69 69 }
运行:
出现了错误的输出。
2.线程间通信-解决安全问题
解决方法:
从两个方面考虑:
1.是否是多线程
2.是否用同一把锁。
改动后:
1 class Res 2 { 3 String name; 4 String sex; 5 6 } 7 class Input implements Runnable 8 { 9 int x=0;10 private Res r;11 public Input(Res r)12 {13 this.r=r;14 15 }16 public void run()17 {18 while(true)19 {20 //添加同步代码块,使用对象r作为锁21 22 synchronized(r)23 {24 if(x==0)25 {26 r.name="Mike";27 r.sex="man";28 }29 else30 {31 r.name="黎黎";32 r.sex="女女女";33 34 }35 36 }37 38 x=(x+1)%2;39 40 }41 42 43 }44 }45 class Output implements Runnable46 {47 private Res r;48 public Output(Res r)49 {50 this.r=r;51 }52 public void run()53 {54 while(true)55 {56 //添加同步代码块,使用对象r作为锁57 synchronized(r)58 {59 System.out.println(r.name+"---"+r.sex);60 61 }62 63 64 }65 66 }67 }68 public class InputOutputDemo69 {70 public static void main(String[] args)71 {72 Res r=new Res();73 Output out=new Output(r);74 Input in=new Input(r);75 76 Thread t1=new Thread(in);77 Thread t2=new Thread(out);78 79 t1.start();80 t2.start();81 }82 }
运行:
不再有错误的输出,但是输出显示大片大片的男,大片大片的女。
3.线程间通信-等待唤醒机制
要实现男女交替输出的效果,要用到wait和notify方法。
wait()
notify()
notifyAll()
都是用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中
只有同步才具有锁。
为什么这些操作线程的方法要定义在Object中呢?
因为这些方法在操作同步中的线程时,都必须标识它们所操作的线程持有的锁,
只有一个锁上的等待线程,可以被同一个锁上的notify唤醒。
不可以对不同锁上的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,而可以被任意对象调用的方法定义在Object中。
修改后:
1 class Res 2 { 3 String name; 4 String sex; 5 //定义布尔型标志位 6 boolean flag=false; 7 8 } 9 class Input implements Runnable10 {11 int x=0;12 private Res r;13 public Input(Res r)14 {15 this.r=r;16 17 }18 public void run()19 {20 while(true)21 {22 synchronized(r)23 {24 //判断标志位,是否等待25 if(r.flag)26 { try{r.wait();}catch(Exception e){}}27 if(x==0)28 {29 r.name="Mike";30 r.sex="man";31 }32 else33 {34 r.name="黎黎";35 r.sex="女女女";36 37 }38 //改变flag值39 r.flag=true;40 //唤醒41 r.notify();42 43 }44 45 x=(x+1)%2;46 47 }48 49 50 }51 }52 class Output implements Runnable53 {54 private Res r;55 public Output(Res r)56 {57 this.r=r;58 }59 public void run()60 {61 while(true)62 {63 synchronized(r)64 {65 //判断标志位,是否等待66 if(!r.flag)67 try{r.wait();}catch(Exception e){}68 System.out.println(r.name+"---"+r.sex);69 r.flag=false;70 //唤醒71 r.notify();72 73 }74 75 76 }77 78 }79 }80 public class InputOutputDemo81 {82 public static void main(String[] args)83 {84 Res r=new Res();85 Output out=new Output(r);86 Input in=new Input(r);87 88 Thread t1=new Thread(in);89 Thread t2=new Thread(out);90 91 t1.start();92 t2.start();93 }94 }
运行:
4.线程间通信-代码优化
数据私有化,通过方法访问。
优化后代码:
1 class Res 2 { 3 //数据成员设置为私有 4 private String name; 5 private String sex; 6 //定义布尔型标志位 7 private boolean flag=false; 8 9 //定义set、get同步方法10 public synchronized void set(String name,String sex)11 {12 if(flag)13 { try{ this.wait();}catch(Exception e){}}14 this.name=name;15 this.sex=sex;16 17 flag=true;18 this.notify();19 }20 public synchronized void get()21 {22 if(!flag)23 { try{ this.wait();}catch(Exception e){}}24 System.out.println(name+"---"+sex);25 26 flag=false;27 this.notify();28 }29 30 }31 class Input implements Runnable32 {33 int x=0;34 private Res r;35 public Input(Res r)36 {37 this.r=r;38 39 }40 public void run()41 {42 while(true)43 {44 if(x==0)45 {46 //调用set方法47 r.set("Mike","man");48 }49 else50 {51 r.set("黎黎","女女女");52 53 }54 x=(x+1)%2;55 }56 57 58 }59 }60 class Output implements Runnable61 {62 private Res r;63 public Output(Res r)64 {65 this.r=r;66 }67 public void run()68 {69 while(true)70 {71 //调用get方法72 r.get();73 }74 75 }76 }77 public class InputOutputDemo78 {79 public static void main(String[] args)80 {81 Res r=new Res();82 //简化83 new Thread(new Input(r)).start();84 new Thread(new Output(r)).start();85 86 87 }88 }
5.线程间通信-生产者消费者
当有多个生产者,多个消费者时,必须循环判断标记,以避免产生生产2个,只消费了一个或类似的情况;
并且使用notifyAll方法,以避免出现所有线程都在等待的情况。
1 class Resource 2 { 3 private String name; 4 private int count=1; 5 private boolean flag=false; 6 7 public synchronized void set(String _name) 8 { 9 while(flag)10 {11 try{wait();}catch(Exception e){}12 }13 name=_name+"--"+count++;14 System.out.println(Thread.currentThread().getName()+"--生产者"+name);15 flag=true;16 notifyAll();17 18 }19 public synchronized void get()20 {21 while(!flag)22 {23 try{wait();}catch(Exception e){}24 }25 System.out.println(Thread.currentThread().getName()+"--------消费者"+name);26 flag=false;27 notifyAll();28 29 }30 }31 class Producer implements Runnable32 {33 private Resource r=new Resource();34 public Producer(Resource r)35 {36 this.r=r;37 }38 public void run()39 {40 while(true)41 {42 r.set("商品");43 }44 }45 }46 class Consumer implements Runnable47 {48 private Resource r=new Resource();49 public Consumer(Resource r)50 {51 this.r=r;52 }53 public void run()54 {55 while(true)56 {57 r.get();58 }59 }60 }61 public class ProducerConsumerDemo62 {63 public static void main(String[] args)64 {65 Resource r=new Resource();66 67 Producer pro=new Producer(r);68 Consumer con=new Consumer(r);69 70 Thread t1=new Thread(pro);71 Thread t2=new Thread(pro);72 Thread t3=new Thread(con);73 Thread t4=new Thread(con);74 75 t1.start();76 t2.start();77 t3.start();78 t4.start();79 80 }81 }
运行: