java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader
1、等待通知机制:
等待通知机制的原理和厨师与服务员的关系很相似:
1,厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间不确定
2,服务员什么时候可以取到菜,必须等到厨师做完之后放到菜品传递台上才行。所以,服务员会等待厨师。
3,厨师做完菜之后放到菜品传递台上,就相当于通知了服务员
wait和notify方法:
1,wait的作用是使当前执行代码的线程进行等待。wait()是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并在wait()所在的代码处停止执行,直到接到通知或者被终端为止。在调用wait()之前,线程必须获得该对象对象级别的锁。在执行wait()之后,当前线程释放锁。在从wait()返回前,该线程和其他线程竞争,从新获取到锁。如果调用wait的时候没有获取到该对象的锁,会抛出异常
2,notify方法也要在同步方法或者同步块中调用,也就是说调用方法之前,线程也必须获得该对象的对象级别的锁。如果调用notify()时没有持有适当的锁也会抛出异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程会话器随机挑选出一个wait状态的状态,对其发出通知,并让他获得该对象的对象锁
代码示例:
threadA先获取到锁,并且wait();threadB后获取到锁,然后通知threadA可以重新获取锁。
如果threadB先获取到锁。notiry()通知时 threadA还未获取到锁,导致threadA wait()后,无人唤醒,永远卡在等人唤醒状态。
/** * @ClassName LockThreadWaitNotify * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/23. */ public class LockThreadWaitNotify { private static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(){ @Override public void run() { synchronized (lock){ System.out.println(getName() + "开始等待....."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "等待结束..."); } }; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁... Thread threadB = new Thread(){ @Override public void run() { /** * 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁 */ synchronized (lock){ try { Thread.sleep(300); System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA"); lock.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } }; // threadA.start();//放在这里执行,有可能threadB先获取到锁... threadB.start(); } }
示例:从0开始计算闰年输出闰年年份
/** * @ClassName LockThreadWaitNotify * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/23. */ public class LockThreadWaitNotify2 { private static Object lock = new Object(); private static volatile int num=0; public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(){ @Override public void run() { synchronized (lock){ while (true){ num++; //计算到闰年就暂停线程 if(num % 4 == 0 && num % 100 != 0){ System.out.println(getName() + "开始等待....."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁... Thread threadB = new Thread(){ @Override public void run() { /** * 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁 */ while (true){ /**System.out.println("num++ 值为:" + num); * 这个获取值要再获取锁代码框之外,因为如果放在synchronized里面 * 会导致释放的锁立刻又被重新获取到,导致threadA无法再次获取到锁 */ System.out.println("闰年为:" + num); synchronized (lock){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA"); lock.notify(); } } } }; // threadA.start();//放在这里执行,有可能threadB先获取到锁... threadB.start(); } }
2、同步代码块 和while循环相互嵌套的差异
while循环在同步代码块内部:
- * while循环在同步代码块内部。锁的定义只被定义了一次,循环是非常快的,
- * 所以lock.notify();后必须要马上lock.wait();把锁抛出去,
- * 否则会因为while循环非常消耗CPU,导致锁瞬间就被重新获取回来,threadA还是获取不到
同步代码块在while循环内部:
- 每次循环完毕都等于自动释放了锁,无需明确指定lock.wait
示例代码:
/** * @ClassName LockThreadWaitNotify * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/23. */ public class LockThreadWaitNotify3 { private static Object lock = new Object(); private static volatile int num=0; public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(){ @Override public void run() { synchronized (lock){ while (true){ num++; //计算到闰年就暂停线程 if(num % 4 == 0 && num % 100 != 0){ System.out.println(getName() + "开始等待....."); lock.notify(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁... Thread threadB = new Thread(){ @Override public void run() { /** * 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁 */ synchronized (lock) { /** * while循环在同步代码块内部。锁的定义只被定义了一次,循环是非常快的, * 所以lock.notify();后必须要马上lock.wait();把锁抛出去, * 否则会因为while循环非常消耗CPU,导致锁瞬间就被重新获取回来,threadA还是获取不到 */ while (true){ System.out.println("闰年为:" + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA"); lock.notify(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; // threadA.start();//放在这里执行,有可能threadB先获取到锁... threadB.start(); } }
3、 线程生命周期
4、wait(等待) & interrupt(停止):
当一个线程处于等待状态的时候,我们不停去停止它,否则会抛异常。并且不能停止掉。
5、notifyAll()
Notify方法每次只会通知一个线程被唤醒:
比如说有2个线程在wait()状态,这个时候,另外一个线程调用了对应的notify方法,这个时候,两个线程中只会有一个被唤醒,另外一个不能被唤醒。如果我们想要唤醒全部,调用notifyAll()方法。
6、wait(long):
表示在指定的时间(毫秒)内暂停线程,时间到达后自动唤醒。类似Thread.sleep()。
7、过早通知问题:
如果notifyAll()、notify() 发生在wait()之前,则没有线程被唤醒。会导致僵死状态。
小练习:做馒头~吃馒头
两个厨师在做馒头,如果厨师发现剩余馒头超过10个,就不做了,每秒钟做1个馒头。有3个食客在吃馒头,如果发现在有馒头可以吃,那么食客随机的吃1到5个馒头。如果馒头不足食客的需求,那么食客等待,让厨师继续做。
import java.util.LinkedList; import java.util.Random; /** * @ClassName LockThreadWaitNotifyMantou * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/24. */ public class LockThreadWaitNotifyMantou { public static void main(String[] args) { LinkedList<String> mantous = new LinkedList<>(); Object lock = new Object(); Runnable product = () -> { while (true){ synchronized (lock){ if (mantous.size() >= 10){ System.out.println(Thread.currentThread().getName()+ "说 库存:"+mantous.size() + "快来吃吧"); lock.notifyAll(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { mantous.add("馒头"); //将指定的元素追加到此列表的末尾。 System.out.println(Thread.currentThread().getName() + "生产了一个馒头," + "库存:" + mantous.size()); lock.notifyAll(); try { // lock.notifyAll(); // lock.wait(1); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; Runnable custumer = () -> { Random random = new Random(); synchronized (lock) { while (true){ Integer buyMantou = Math.abs(random.nextInt()) % 5 + 1; if (mantous.size() >= buyMantou){ //吃馒头 System.out.println(Thread.currentThread().getName() + "开始吃馒头"); try { //慢点吃, Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //吃 for (int i=0; i<buyMantou; i++){ mantous.poll(); } //吃完了 System.out.println(Thread.currentThread().getName() + "吃了" + buyMantou + "个馒头,还剩" + mantous.size()); }else { //没得吃,不够吃 System.out.println(Thread.currentThread().getName() + "想吃" + buyMantou + "只有" +mantous.size() + "个了"); System.out.println(Thread.currentThread().getName() +":师傅快做馒头吧...."); lock.notifyAll(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; Thread thread = new Thread(product,"厨师1"); Thread thread1 = new Thread(product,"厨师2"); Thread thread2 = new Thread(custumer,"饭桶1"); Thread thread3 = new Thread(custumer,"饭桶2"); Thread thread4 = new Thread(custumer,"饭桶3"); thread1.start(); thread.start(); thread2.start(); thread4.start(); thread3.start(); } }
管道通信:
在JAVA语言中,提供了各种各样的输入流/输出流 stream, 让我们可以很方便的进行数据操作。其中管道流是一种特殊的流。。专门用在不同线程之间直接传送数据一个线程从输出管道中写入数据,另外一个线程从数据管道中读取数据,而无需借助临时文件之类的东西。
字节流:
- PipedInputStream;PipedOutputStream;
字符流:
- PipedWriter; PipedReader
字节流示例代码:
注意,流用完了必须关闭否则报错:java.io.IOException: Write end dead
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; /** * @ClassName ThreadConnectInStream * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/24. */ public class ThreadConnectInStream { public static void main(String[] args) { /** * 演示字节流输入输出对接。 */ PipedInputStream pipedInputStream = new PipedInputStream(); PipedOutputStream pipedOutputStream = new PipedOutputStream(); //关联管道输入流输出流 try { pipedInputStream.connect(pipedOutputStream); } catch (IOException e) { e.printStackTrace(); } Thread diaosi = new Thread(){ @Override public void run() { try { Thread.sleep(1000); pipedOutputStream.write("女神好".getBytes()); pipedOutputStream.flush(); Thread.sleep(1000); pipedOutputStream.write("女神sss好".getBytes()); } catch (InterruptedException | IOException e) { e.printStackTrace(); }finally { try { pipedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }; Thread nvShen = new Thread(){ @Override public void run() { int len = -1; byte[] bytes = new byte[1024]; try { while ((len = pipedInputStream.read(bytes)) != -1) { System.out.println("收到消息"); String string = new String(bytes,0,len); System.out.println(string); } }catch (IOException e) { e.printStackTrace(); }finally { try { pipedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }; diaosi.start(); nvShen.start(); } }
流不关闭则抛异常
字符流示例代码:
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedReader; import java.io.PipedWriter; /** * @ClassName ThreadConnectInWriter * @projectName: object1 * @author: Zhangmingda * @description: XXX * date: 2021/4/24. */ public class ThreadConnectInWriter { public static void main(String[] args) { /** * 字符输入输出流对接 */ PipedWriter pwriter = new PipedWriter(); PipedReader pReader = new PipedReader(); try { pReader.connect(pwriter); } catch (IOException e) { e.printStackTrace(); } Thread writer = new Thread(){ @Override public void run() { try { pwriter.write("我爱你"); Thread.sleep(1000); pwriter.flush(); pwriter.write("真的"); } catch (InterruptedException | IOException e) { e.printStackTrace(); }finally { try { pwriter.close(); } catch (IOException e) { e.printStackTrace(); } } } }; Thread reader = new Thread(){ @Override public void run() { try { int len = -1; char[] chars = new char[1024] ; while ((len = pReader.read(chars)) != -1){ String string = new String(chars,0,len); System.out.println(string); } } catch (IOException e) { e.printStackTrace(); }finally { try { pReader.close(); } catch (IOException e) { e.printStackTrace(); } } } }; writer.start(); reader.start(); } }
posted on 2021-04-23 16:20 zhangmingda 阅读(165) 评论(0) 编辑 收藏 举报