【java多线程编程核心技术】3.线程间通信 -笔记总结
来源:互联网 发布:淘宝账期延长十五天 编辑:程序博客网 时间:2024/06/07 14:00
等待/通知机制
线程与线程之间不是独立的个体,他们彼此之间可以互相通信和协作。
不使用等待/通知机制实现线程通信
可以通过不停地while语句轮询机制来检测某一个条件,但这样特别耗费CPU资源。(轮询间隔时间小,更浪费CPU资源,如果间隔时间大,有可能获取不到想要的数据)
什么是等待/通知机制
餐厅里厨师与服务员之间的交互模式就属于等待/通知机制。
服务员去到菜的时间取决于厨师,所以服务员就有“等待(wait)”状态;
厨师做好菜放在“菜品传递台”上,其实就相当于一种“通知(notify)”;
等待/通知机制的实现
wait 使线程停止运行,notify 使停止的线程继续运行。
关键字 synchronized 可以将任何一个 Object 对象作为同步对象看待,而 Java 为每个 Object 都实现了 wait() 和 notify() 方法,他们必须用在被 synchronized 同步的 Object 的临界区内。
通过调用 wait 方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而 notify 操作可以唤醒一个因调用了 wait 方法而处于阻塞状态的线程,使其进入就绪状态。
被重新唤醒的线程会试图重新获得临界区的控制权,继续执行临界区内 wait 之后的代码。
wait 方法可以使调用该方法的线程释放共享资源的锁,从运行状态退出,进入等待状态,直到再次被唤醒。
notify() 方法可以随机唤醒等待对列中等待同一共享资源的一个线程,并使该线程退出等待状态,进入可运行状态。
notifyAll() 方法可以随机唤醒等待对列中等待同一共享资源的所有线程,并使这些线程退出等待状态,进入可运行状态。
敲黑板,划重点:wait()立即释放锁,notify等线程执行完后再释放。
Java为每一个Object都实现了wait()和notify()方法,但它们必须用在被synchronized同步的Object的临界区,否则会抛出异常(IllegalMonitorStateException)
新创建一个线程对象后,在调用它的 start() 方法,系统会为此线程分配 CPU 资源,使其处于 Runnable(可运行)状态,如果线程抢占到 CPU 资源,此线程就会处于 Running (运行)状态
Runnable 和 Running 状态之间可以相互切换,因为线程有可能运行一段时间后,有其他优先级高的线程抢占了 CPU 资源,此时线程就从 Running 状态变成了 Runnable 状态。
线程进入 Runnable 状态大致有如下五种情况:
* 调用 sleep() 方法后经过的时间超过了指定的休眠时间
* 线程调用的阻塞 IO 已经返回,阻塞方法执行完毕
* 线程成功的获得了试图同步的监视器
* 线程正在等待某个通知,其他线程发出了通知
* 处于挂状态的线程调用了 resume 恢复方法
Blocked 是阻塞的意思,例如线程遇到一个 IO 操作,此时 CPU 处于空闲状态,可能会转而把 CPU 时间片分配给其他线程,这时也可以称为 “暂停”状态。Blocked 状态结束之后,进入 Runnable 状态,等待系统重新分配资源
出现阻塞状态的有如下五种情况:
* 线程调用 sleep 方法,主动放弃占用的处理器资源
* 线程调用了阻塞式 IO 方法,在该方法返回之前,该线程被阻塞
* 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有
* 线程等待某个通知
* 程序调用了 suspend 方法将该线程挂起
run 方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都是两个队列,一个是就绪队列,一个是阻塞队列
就绪队列:将要获得锁的线程,一个线程被唤醒后,才会进入就绪队列,等待CPU的调度
阻塞队列:被阻塞的线程,例如被wait后,就会进入阻塞队列
方法wait()锁释放与notify()锁不释放
当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。
当interrupt方法遇到wait方法
当线程呈wait()状态时,调用线程对象的interrupt()方法会抛出如下异常
begin wait()java.lang.InterruptedException出现异常了,因为呈wait状态的线程被interrupt了! at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Unknown Source) at service.Service.testMethod(Service.java:9) at extthread.ThreadA.run(ThreadA.java:17)
通知过早
如果通知过早,则会打乱程序正常的运行逻辑,例如在先notify()后,再wait(),会导致线程一直处于wait状态,不会被通知。
等待wait的条件发生变化
public class Add { private String lock; public Add(String lock) { super(); this.lock = lock; } public void add() { synchronized (lock) { ValueObject.list.add("anyString"); lock.notifyAll(); } }}public class Subtract { private String lock; public Subtract(String lock) { super(); this.lock = lock; } public void subtract() { try { synchronized (lock) { if (ValueObject.list.size() == 0) {// while (ValueObject.list.size() == 0) { System.out.println("wait begin ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println("wait end ThreadName=" + Thread.currentThread().getName()); } ValueObject.list.remove(0); System.out.println("list size=" + ValueObject.list.size()); } } catch (InterruptedException e) { e.printStackTrace(); } }}public class ValueObject { public static List list = new ArrayList();}public class ThreadAdd extends Thread { private Add p; public ThreadAdd(Add p) { super(); this.p = p; } @Override public void run() { p.add(); }}public class ThreadSubtract extends Thread { private Subtract r; public ThreadSubtract(Subtract r) { super(); this.r = r; } @Override public void run() { r.subtract(); }}public class Run { public static void main(String[] args) throws InterruptedException { String lock = new String(""); Add add = new Add(lock); Subtract subtract = new Subtract(lock); ThreadSubtract subtract1Thread = new ThreadSubtract(subtract); subtract1Thread.setName("subtract1Thread"); subtract1Thread.start(); ThreadSubtract subtract2Thread = new ThreadSubtract(subtract); subtract2Thread.setName("subtract2Thread"); subtract2Thread.start(); Thread.sleep(1000); ThreadAdd addThread = new ThreadAdd(add); addThread.setName("addThread"); addThread.start(); }}在if (ValueObject.list.size() == 0)情况下,输出结果:wait begin ThreadName=subtract1Threadwait begin ThreadName=subtract2Threadwait end ThreadName=subtract2Threadlist size=0wait end ThreadName=subtract1ThreadException in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(Unknown Source) at java.util.ArrayList.remove(Unknown Source) at entity.Subtract.subtract(Subtract.java:24) at extthread.ThreadSubtract.run(ThreadSubtract.java:16)更换为while(ValueObject.list.size() == 0)情况下,输出结果:wait begin ThreadName=subtract2Threadwait begin ThreadName=subtract1Threadwait end ThreadName=subtract1Threadlist size=0wait end ThreadName=subtract2Threadwait begin ThreadName=subtract2Thread
出现异常的原因:实现了两次删除remove()操作,第一次能正常删除,第二次则报异常
用while(…)替换掉if(…),当线程被通知(notify)后,会再进一次进行判断长度是非为0。
生产者/消费者模式实现
一生产与一消费:操作值
生产者:
package entity;//生产者public class P { private String lock; public P(String lock) { super(); this.lock = lock; } public void setValue() { try { synchronized (lock) { if (!ValueObject.value.equals("")) { lock.wait(); } String value = System.currentTimeMillis() + "_" + System.nanoTime(); System.out.println("set的值是" + value); ValueObject.value = value; lock.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } }}
消费者:
package entity;//消费者public class C { private String lock; public C(String lock) { super(); this.lock = lock; } public void getValue() { try { synchronized (lock) { if (ValueObject.value.equals("")) { lock.wait(); } System.out.println("get的值是" + ValueObject.value); ValueObject.value = ""; lock.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } }}
存储值的对象:
package entity;public class ValueObject { public static String value = "";
生产者线程:
package extthread;import entity.P;public class ThreadP extends Thread { private P p; public ThreadP(P p) { super(); this.p = p; } @Override public void run() { while (true) { p.setValue(); } }}
消费者线程:
package extthread;import entity.C;public class ThreadC extends Thread { private C r; public ThreadC(C r) { super(); this.r = r; } @Override public void run() { while (true) { r.getValue(); } }}
运行类:
package test;import entity.P;import entity.C;import extthread.ThreadP;import extthread.ThreadC;public class Run { public static void main(String[] args) { String lock = new String(""); P p = new P(lock); C r = new C(lock); ThreadP pThread = new ThreadP(p); ThreadC rThread = new ThreadC(r); pThread.start(); rThread.start(); }}输出结果:set的值是1511090029379_209828520790577get的值是1511090029379_209828520790577set的值是1511090029379_209828520806165get的值是1511090029379_209828520806165set的值是1511090029379_209828520837751get的值是1511090029379_209828520837751set的值是1511090029379_209828520848006get的值是1511090029379_209828520848006set的值是1511090029379_209828520858262get的值是1511090029379_209828520858262set的值是1511090029379_209828520868517get的值是1511090029379_209828520868517set的值是1511090029379_209828520885336get的值是1511090029379_209828520885336
多生产与多消费:操作值-假死
在未将notify()更换为notifyAll()之前,容易造成假死现象。
“假死”现象其实就是线程进入waiting等待状态。如果全部的线程都进入waiting状态,则呈现就不再执行任何业务功能了,整个项目呈停止状态。
假死出现的主要原因是有可能连续唤醒同类,导致所有线程呈waiting状态。
在将notify更换为notifyAll()以后,解除假死现象。
需要注意的是,在多生产与多消费中,一定要将if (ValueObject.value.equals(“”)) 更换为while(….),理由同上一节。
通过管道进行线程间通信:字节流/字符流
管道流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。
通过使用管道,实现不同线程间的通信,而无需借助类似于临时文件类的东西
1.PipedInputStream和PipedOutputStream
2.PipedReader和PipedWriter
public class ThreadRead extends Thread { private ReadData read; private PipedInputStream input; public ThreadRead(ReadData read, PipedInputStream input) { super(); this.read = read; this.input = input; } @Override public void run() { read.readMethod(input); }}package extthread;import java.io.PipedOutputStream;import service.WriteData;public class ThreadWrite extends Thread { private WriteData write; private PipedOutputStream out; public ThreadWrite(WriteData write, PipedOutputStream out) { super(); this.write = write; this.out = out; } @Override public void run() { write.writeMethod(out); }}package service;import java.io.IOException;import java.io.PipedInputStream;public class ReadData { public void readMethod(PipedInputStream input) { try { System.out.println("read :"); byte[] byteArray = new byte[20]; int readLength = input.read(byteArray); while (readLength != -1) { String newData = new String(byteArray, 0, readLength); System.out.print(newData); readLength = input.read(byteArray); } System.out.println(); input.close(); } catch (IOException e) { e.printStackTrace(); } }}package service;import java.io.IOException;import java.io.PipedOutputStream;public class WriteData { public void writeMethod(PipedOutputStream out) { try { System.out.println("write :"); for (int i = 0; i < 300; i++) { String outData = "" + (i + 1); out.write(outData.getBytes()); System.out.print(outData); } System.out.println(); out.close(); } catch (IOException e) { e.printStackTrace(); } }}package test;import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutputStream;import service.ReadData;import service.WriteData;import extthread.ThreadRead;import extthread.ThreadWrite;public class Run { public static void main(String[] args) { try { WriteData writeData = new WriteData(); ReadData readData = new ReadData(); PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(); // inputStream.connect(outputStream); outputStream.connect(inputStream); ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream); threadWrite.start(); Thread.sleep(2000); ThreadRead threadRead = new ThreadRead(readData, inputStream); threadRead.start(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }}输出结果:write :1234567891011121314151 ......read :1234567891011121314151........
通过 // inputStream.connect(outputStream);// outputStream.connect(inputStream); 使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入。
通过管道进行线程间通信:字符流
public void readMethod(PipedReader input) { try { System.out.println("read :"); char[] byteArray = new char[20]; int readLength = input.read(byteArray); while (readLength != -1) { String newData = new String(byteArray, 0, readLength); System.out.print(newData); readLength = input.read(byteArray); } System.out.println(); input.close(); } catch (IOException e) { e.printStackTrace(); } }.........大部分与字节流类似
实战:等待/通知之交叉备份
(最近也在琢磨做一个真正能派上用场的多线程备份数据库的小项目,后期完成了会在这里贴一个后续链接,先占个位吧~ 哈哈哈哈哈, 2017年11月19日留)
目标:创建20个线程,其中10个线程是将数据备份到数据库A,另外10个线程将数据备份到数据库B中去,并且备份数据库A和备份数据库B是交叉进行的。
DBTools:
package service;public class DBTools { volatile private boolean prevIsA = false; synchronized public void backupA() { try { while (prevIsA == true) { wait(); } for (int i = 0; i < 5; i++) { System.out.println("★★★★★"); } prevIsA = true; notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void backupB() { try { while (prevIsA == false) { wait(); } for (int i = 0; i < 5; i++) { System.out.println("☆☆☆☆☆"); } prevIsA = false; notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } }
BackupA/BackupB:
package extthread;import service.DBTools;public class BackupA extends Thread { private DBTools dbtools; public BackupA(DBTools dbtools) { super(); this.dbtools = dbtools; } @Override public void run() { dbtools.backupA(); }}package extthread;import service.DBTools;public class BackupB extends Thread { private DBTools dbtools; public BackupB(DBTools dbtools) { super(); this.dbtools = dbtools; } @Override public void run() { dbtools.backupB(); }}
Run:
package test.run;import service.DBTools;import extthread.BackupA;import extthread.BackupB;public class Run { public static void main(String[] args) { DBTools dbtools = new DBTools(); for (int i = 0; i < 20; i++) { BackupB output = new BackupB(dbtools); output.start(); BackupA input = new BackupA(dbtools); input.start(); } }}结果:★★★★★★★★★★★★★★★★★★★★★★★★★☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆★★★★★★★★★★★★★★★★★★★★★★★★★.........
交替打印的原理就是:volatile private boolean prevIsA=false;
方法join的使用
join():等待线程对象销毁。
用join()方法来解决
package extthread;public class MyThread extends Thread { @Override public void run() { try { int secondValue = (int) (Math.random() * 10000); System.out.println(secondValue); Thread.sleep(secondValue); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}package test;import extthread.MyThread;public class Test { public static void main(String[] args) { try { MyThread threadTest = new MyThread(); threadTest.start(); threadTest.join(); System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了"); } catch (InterruptedException e) { e.printStackTrace(); } }}输出结果:9715我想当threadTest对象执行完毕后我再执行,我做到了
join 方法具有使线程排队运行的作用,有些类似同步的运行效果。join 与 synchronized 的区别是:join 在内部使用 wait() 方法进行等待,而 synchronized 关键字使用的是 “对象监视器” 原理做为同步。
方法join与异常
在join过程中,如果当前线程对象被中断(interrupt),则当前线程出现异常(InterruptedException)。
方法join(long)的使用
方法join(long)中的参数是设定是等待的时间
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
只等待XXXX毫秒时间
方法join(long)与sleep(long)的区别
方法join(long)的功能是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点
而Thread.sleep(long)方法却不释放锁!
类ThreadLocal的使用
通过类ThreadLocal实现每一个线程都有自己的共享变量,解决的就是每个线程绑定自己的值。
线程变量的隔离性
package extthread;import tools.Tools;public class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 100; i++) { if (Tools.tl.get() == null) { Tools.tl.set("ThreadA" + (i + 1)); } else { System.out.println("ThreadA get Value=" + Tools.tl.get()); } Thread.sleep(200); } } catch (InterruptedException e) { e.printStackTrace(); } }}package extthread;import tools.Tools;public class ThreadB extends Thread { @Override public void run() { try { for (int i = 0; i < 100; i++) { if (Tools.tl.get() == null) { Tools.tl.set("ThreadB" + (i + 1)); } else { System.out.println("ThreadB get Value=" + Tools.tl.get()); } Thread.sleep(200); } } catch (InterruptedException e) { e.printStackTrace(); } }}package tools;public class Tools { public static ThreadLocal tl = new ThreadLocal();}package test;import tools.Tools;import extthread.ThreadA;import extthread.ThreadB;public class Run { public static void main(String[] args) { try { ThreadA a = new ThreadA(); ThreadB b = new ThreadB(); a.start(); b.start(); for (int i = 0; i < 100; i++) { if (Tools.tl.get() == null) { Tools.tl.set("Main" + (i + 1)); } else { System.out.println("Main get Value=" + Tools.tl.get()); } Thread.sleep(200); } } catch (InterruptedException e) { e.printStackTrace(); } }}输出结果:ThreadB get Value=ThreadB1Main get Value=Main1ThreadA get Value=ThreadA1ThreadB get Value=ThreadB1Main get Value=Main1ThreadA get Value=ThreadA1Main get Value=Main1ThreadB get Value=ThreadB1ThreadA get Value=ThreadA1Main get Value=Main1...........
解决get()返回null问题
通过继承ThreadLocal类,重写 initialValue方法
package ext;public class ThreadLocalExt extends ThreadLocal { @Override protected Object initialValue() { return "我是默认值 第一次get不再为null"; }}
实现首次get()不为null;
InheritableThreadLocal 类的使用
该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。
通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数(parentValue+“XXXX”)。
- 【java多线程编程核心技术】3.线程间通信 -笔记总结
- 《java多线程编程核心技术》之线程间通信
- 《java多线程编程核心技术》读书笔记3:线程间的通信
- Java多线程编程核心技术---线程间通信(一)
- Java多线程编程核心技术---线程间通信(二)
- java多线程编程核心技术3-线程间通信
- Java多线程核心技术(三):线程间通信
- 《Java多线程编程核心技术》(三)线程通信
- 【java多线程编程核心技术】1.java多线程技能-笔记总结
- 《Java多线程编程核心技术》学习笔记(二)——多线程间通信
- java多线程编程核心技术笔记-停止线程interrupt()方法
- java多线程核心技术 第三章线程间通信
- Java多线程编程核心技术笔记
- 《Java多线程编程核心技术》笔记
- 《Java多线程编程核心技术》-笔记
- 《Java 多线程编程核心技术》学习笔记及总结
- 【java多线程编程核心技术】4.Lock的使用-笔记总结
- 【java多线程编程核心技术】5.定时器Timer-笔记总结
- 线段树区间查询
- 使用selenium3.X启动firefox浏览器
- b进制运算之一--和
- The more, The Better (树形dp+背包)
- 445. Add Two Numbers II(JAVA)
- 【java多线程编程核心技术】3.线程间通信 -笔记总结
- 3Sum Closest Leetcode
- 前端学习知识要点整理
- Mybatis在控制台显示sql语句配置文件
- jq滚动到顶部
- 引用 欢迎使用CSDN-markdown编辑器
- 个人总结40
- ubuntu服务器基本安全配置
- Centos7安装Torque