第3章 线程间通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。
wait / notify
方法
wait()
的作用是使当前执行代码的线程进行等待,wait()
方法是Object
类的方法,该方法用来将当前线程置入"预执行队列"中,并且在wait()
所在的代码处(马上)停止执行,直到接到通知或被中断为止。在调用
wait()
之前,线程必须获得该对象的对象级别的锁,即只能在同步方法或同步块中调用wait()
方法。在wait()
方法执行后,当前线程释放锁。在从
wait()
返回 前,线程与其他线程竞争获得锁。如果调用wait()
时没有持有适当的锁,则会抛出IllegalMonitorStateException
,它是RuntimeException
的一个子类,因此不需要try-catch语句进行捕捉异常。方法
notify()
也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()
时没有持有适当的锁,也会抛出IllegalMonitorStateException
。notify()
方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态线程,对其发出通知notify,并使它等待获得该对象的对象锁。需要说明的是,在执行
notify()
方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()
方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。如果发出notify命令时没有处于阻塞状态中的线,那么该命令会被忽略。
用一句话来总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。
wait()
方法可以使调用该方法的线程释放共享资源的锁, 然后从运行状态退出, 进入等待队列,直到被再次唤醒。notify()
方法可以随机唤醒等待队列中等待同一个共享资源的“一个”线程,并使该线程退出等待队列, 进入可运行状态, 也就是notify()
方法仅通知“一个”线程。notifyAll()
方法可以使所有正在等待队列中等待同一个共享资源的“全部”线程从等待状态退出, 进入可运行状态。 此时, 优先级最高的那个线程最先执行, 但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的对象,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之, 一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
必须执行完
notify()
方法所在的同步synchronized代码块后才释放锁。
1)执行完同步代码块就会释放对象的锁。
2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
while (list.size() == 1){
this.wait();//待下次获得锁时接着执行while中的代码
}
通过管道进行线程间通信:字节流/字符流
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedReader;
//读字节流/字符流
public class ReadData {
public void readMethod(PipedInputStream inputStream) {
try {
System.out.println("read :");
byte[] byteArray = new byte[20];
int readLength = inputStream.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.println(newData);
readLength = inputStream.read(byteArray);
}
System.out.println();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void readCharIO(PipedReader reader) {
try {
System.out.println("read :");
char[] charArray = new char[20];
int readLength = reader.read(charArray);
while (readLength != -1) {
String newData = new String(charArray, 0, readLength);
System.out.println(newData);
readLength = reader.read(charArray);
}
System.out.println();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import java.io.PipedOutputStream;
import java.io.PipedWriter;
//写字节流/字符流
public class WriteData {
public void writeMethod(PipedOutputStream outputStream) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
outputStream.write(outData.getBytes());
System.out.println(outData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeCharIO(PipedWriter writer) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
writer.write(outData);
System.out.println(outData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
//测试
public class Run_W_R {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream = new PipedInputStream();
PipedWriter writer = new PipedWriter();
PipedOutputStream outputStream = new PipedOutputStream();
PipedReader reader = new PipedReader();
//inputStream.connect(outputStream);// 连接,这两行写其中一个就行
outputStream.connect(inputStream);
writer.connect(reader);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream, writer);
threadWrite.start();
Thread.sleep(2000);
ThreadRead threadRead = new ThreadRead(readData, inputStream, reader);
threadRead.start();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
join
在很多情况下, 主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时, 如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个灵气,主线程要取得这个子线程中的值,就要用到
join()
方法了。方法join()
的作用是等待线程对象销毁。主线程与子线程是相对而言的,并非指main线程,如:
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}
说明:
上面的有两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
在Father主线程中,通过new Son()新建“子线程s”。接着通过s.start()启动“子线程s”,并且调用s.join()。在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;在“子线程s”运行完毕之后,Father主线程才能接着运行。 这也就是我们所说的“join()的作用,是让主线程会等待子线程结束之后才能继续运行”!
- 方法join具有使线程排除运行的作用,有些类似同步的运行效果。join与synchroinzed的区别是:join在内部使用
wait()
方法进行等待,而sychronized关键字使用的是“对象临界视器”原理做为同步。
/**
* java version "1.7.0_79"
* Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
* Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
*/
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);//所以join()会让出CPU
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- 方法
join(long)
中的参数是设定等待的时间。
threadTest.join(2000);//只等2秒
- 方法
join(long)
的功能在内部是使用wait(long)
方法来实现的, 所以join(long)
方法是具有释放锁的特点。而Thread.sleep(long)
方法却不释放锁。
ThreadLocal / InheritableThreadLocal
多个类间数据共享,例Controller中想用其它类中某个方法的结果,但又不能调用等。
变量wfhgr共享可以使用
publi static
变量的形式,所有的线程都使用同一个public static
变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的类ThreadLocal
正是为了解决这样的问题。类
ThreadLocal
主要解决的就是每个线程绑定自己的值,可以将ThreadLocal
类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。也就是不同线程拥有自己的值。
public class Run {
public static ThreadLocal tl = new ThreadLocal();
publi static void main(String[] args) {
if (tl.get() == null) {
System.out.println("从未放过值");
tl.set("我的值");//赋值
}
System.out.println(tl.get());//取值
}
}
- 增加ThreadLoca功能可以继承ThreadLocal类,如使默认
get()
为不为null
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
//return null;
return "default value";
}
}
-
InheritableThreadLocal
可以在子线程中取得父线程继承下来的值。但如果子线程中也set()
进去值了此时父线程和子线程分别分别保存自己的值。
class Tools {
public static InheritableThreadLocal tl = new InheritableThreadLocal();
}
class Father extends Thread {
@Override
public void run() {
Tools.tl.set("Father thread");
Son son = new Son();
son.start();
try {
son.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("F :" + Tools.tl.get());
}
}
//子线程中没有赋值时可以取到父线程中存入的值
//子线程中写入自己的值时取到的就是自己的值
class Son extends Thread {
@Override
public void run() {
//Tools.tl.set("Son thread");
System.out.println("S :" + Tools.tl.get());
}
}
class FS {
public static void main(String[] args) throws InterruptedException {
Father father = new Father();
father.start();
father.join();
System.out.println("end");
}
}
但在使用InheritableThreadLocal类需要注意一点的是, 如果子线程在取得值的同时, 主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。