3.1 等待/通知机制
3.1.1 不适用等待/通知机制实现线程间通信
可以使用sleep+while死循环的方式进行多个线程间的通信。但浪费CPU资源。
3.1.2 什么是等待/通知机制
3.1.3 等待/通知机制的实现
- wait():作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或者被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或者同步快中调用wait()方法。在执行wait()方法后,当前线程释放锁。如果在调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateExcepion,它是RunntimeException的一个子类,因此,不需要try-catch语句进行捕获异常。
- notify():也要在同步方法或者同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果在调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateExcepion。该方法用来通知那些可能等待该对象的对象锁的线程,如果有多个线程等待,则由线程规划器随机挑出其中一个呈wait()状态的线程,对其发出通知notify(一次notify方法只能通知一个执行wait方法的线程),并使它等待获取该对象的对象锁。需要注意的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait()状态的线程并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait()状态所在线程才可以获取该对象锁。当第一个获取该对象锁wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次调用notify语句,则即便该对象已空闲,其他wait状态等待线程由于没有收到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll,如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。
- notifyAll():可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个想成优先执行,但有可能是随机执行,因为这取决于虚拟机的实现。
3.1.4 方法wait()锁释放与notify()锁不释放
执行wait()方法,锁被自动释放,执行完notify()方法后,锁不自动释放。
3.1.5 当interrupt方法遇到wait方法
- 当线程呈wait()状态时,调用线程对象的interrupt方法会出现InterruptedException异常。
- 执行完代码块就会释放对象锁。
- 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
3.1.6 只执行一个线程
notify()
3.1.7 唤醒所有线程
notifyAll()
3.1.8 方法wait(long)的使用
wait(long arg)方法的功能是等待一段时间,在这段时间内是否有线程对持有的锁对象进行唤醒,如果超过了这段时间,该锁就自动唤醒。
3.1.9 通知过早
如果通知过早,则会打乱程序正常的运行逻辑。
3.1.10 等待wait的条件发生变化
wait等待的条件发生了变化,也容易造成程序逻辑的混乱。
3.1.11 生产者/消费者模式实现
注意"假死"状态。方法是使用notifyAll()。
3.1.12 通过管道进行线程间通信:字节流
- PipedInputStream、PipedOutputStream
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);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException 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 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();
}
}
}
3.1.13 通过管道进行线程间通信:字符流
pipedReader、pipedWriter
代码通3.1.12,只是原先的字节流改为字符流
3.1.14 实战:等待/通知之交叉备份
3.2 方法join的使用
3.2.1 学习方法join前的铺垫
3.2.2 用join()方法来解决
- 方法x.join()的作用是使所属线程x 正常执行run()中的方法,而使得调用x.join()的线程z处于无限期阻塞状态,等待x线程销毁后再继续执行线程z后面的代码。
- 方法join()具有使线程排队运行的作用,有些类似于同步的运行效果。join()与synchronized的区别是:join在内部调用wait()方法进行等待,而synchronized关键字使用的是"对象监视器"原理作为同步。
3.2.3 方法join与异常
在join过程中,如果当前线程对象被中断,则当前线程出现异常。方法join()与interrupt()方法如果遇到彼此,则会出现异常。
3.2.4 方法join(long)的使用
设置等待时间
3.2.5 方法join(long)与sleep(long)的区别
从源代码中可以发现,join(long)方法内部使用wait(long)实现,所以join(long)方法执行后会释放锁,所以其他线程就可以调用此线程中的同步方法。sleep(long)不释放锁。
3.2.6 方法join()后面的代码提前运行:出现意外
3.2.6 方法join()后面的代码提前运行:解释意外
join(100)多数情况下先抢到锁
3.3 类ThreadLocal的使用
使用public static 可以使所有的线程公用一个变量。ThreadLocal类实现每一个线程都有自己的共享变量。
3.3.1 方法get()与null
package test1;
public class Run {
public static ThreadLocal t1 = new ThreadLocal<>();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("从未放过值");
t1.set("a");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
3.3.2 验证线程变量的隔离性
3.3.3 解决get()返回null问题
public class ThreadLocalExt extends ThreadLocal {
protected Object initialValue() {
// 初始值为a,不再为null
return "a";
}
}
3.3.4 再次验证线程变量的隔离性
3.4 类InheritableThreadLocal的使用
InheritableThreadLocal用于子线程继承父线程的数值。
3.4.1 值继承
public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
public class Tools {
public static InheritableThreadLocalExt t1=new InheritableThreadLocalExt();
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadA extends Thread{
@Override
public void run() {
try{
for (int i = 0; i <10; i++) {
System.out.println("在ThreadA线程中取值= "+Tools.t1.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在Main线程中取值="+Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a=new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.4.2 值继承再修改
public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
@Override
protected Object childValue(Object parentValue) {
return parentValue+" 我在子线程加的~";
}
}
public class Tools {
public static InheritableThreadLocalExt t1=new InheritableThreadLocalExt();
}
public class ThreadA extends Thread{
@Override
public void run() {
try{
for (int i = 0; i <10; i++) {
System.out.println("在ThreadA线程中取值= "+Tools.t1.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在Main线程中取值="+Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a=new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
但在使用InheritableThreadLocal类需要注意一点的是,如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。