3.1线程间的通讯机制
3.1.1等待/通知机制的实现
方法wait()的作用是让当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置于“预执行队列”,并且在wait()所在的代码处停止执行,直到接到通知或者中断为止。在调用wait()方法前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法,在执行wait()方法后,当前线程释放锁。在wait()返回后,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException异常,它是RuntimeExcpetion异常的一个子类,因此不需要try-catch语句进行捕获异常。
方法notify()也要在同步块或同步方法中调用,调用前线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划期随机挑出其中一个呈wait状态的线程,对其发出通知notify,在执行notify方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁。当第一个获得该对象锁的wait线程运行完毕以后,它会释放该对象锁,此时如果对象没有再次使用notify语句,即该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或者notifyAll。
当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterrutedException异常,在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
3.1.2多生产与多消费:操作栈
本实例是使用生产者向栈List对象中放入数据,使用消费者从List栈中取出数据,List最大容量是1,实验环境是多个生产者与多个消费者。
自定义栈类,写两个同步的push和pop方法,进行等待和通知操作。
push方法:如果栈中的list的大小为1,则调用wait等待,否则添加一个元素,调用notifyAll()方法通知其他线程。
pop方法:如果栈中的list的大小为0,则调用wait等待,否则移除一个元素,调用notifyAll()方法通知其他线程。
package p3;
import java.util.ArrayList;
import java.util.List;
public class MyStack {
private List list=new ArrayList();
synchronized public void push() {
try {
while(list.size()==1) {
System.out.println("push操作中的"+Thread.currentThread().getName()+" 线程呈wait状态");
this.wait();
}
Thread.sleep(1000);
list.add("push "+Math.random());
this.notifyAll();;
System.out.println("push size "+list.size());
}catch(InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void pop() {
try {
while(list.size()==0) {
System.out.println("pop操作中的"+Thread.currentThread().getName()+" 线程呈wait状态");
this.wait();
}
Thread.sleep(1000);
list.remove(0);
this.notifyAll();;
System.out.println("pop size "+list.size());
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
生产者线程
package p3;
public class ThreadPush extends Thread{
private MyStack myStack;
public ThreadPush(MyStack myStack) {
this.myStack=myStack;
}
public void run() {
while(true) {
myStack.push();
}
}
}
消费者线程
package p3;
public class ThreadPop extends Thread{
private MyStack myStack;
public ThreadPop(MyStack myStack) {
this.myStack=myStack;
}
public void run() {
while(true) {
myStack.pop();
}
}
}
主函数,创建多个生产者消费者线程。
package p3;
public class Run {
public static void main(String[] args) {
MyStack myStack=new MyStack();
ThreadPop threadPop1=new ThreadPop(myStack);
ThreadPop threadPop2=new ThreadPop(myStack);
ThreadPop threadPop3=new ThreadPop(myStack);
ThreadPush threadPush1=new ThreadPush(myStack);
ThreadPush threadPush2=new ThreadPush(myStack);
ThreadPush threadPush3=new ThreadPush(myStack);
threadPop1.start();
threadPop2.start();
threadPop3.start();
threadPush1.start();
threadPush2.start();
threadPush3.start();
}
}
3.1.3 通过管道进行线程间的通讯:字节流
管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道读数据,通过使用管道,实现不同线程间的通讯,而无需借助类似临时文件之类的东西。
写入管道数据线程类
package p3;
import java.io.PipedOutputStream;
public class ThreadWrite extends Thread {
private PipedOutputStream out;
private HandlerData handlerData;
public ThreadWrite(HandlerData handlerData,PipedOutputStream out) {
this.handlerData=handlerData;
this.out=out;
}
public void run() {
handlerData.writeMethod(out);
}
}
读取管道数据线程类
package p3;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class ThreadRead extends Thread {
private PipedInputStream in;
private HandlerData handlerData;
public ThreadRead(HandlerData handlerData,PipedInputStream in) {
this.handlerData=handlerData;
this.in=in;
}
public void run() {
handlerData.readMethod(in);
}
}
处理数据类,包含读取、写入方法。
package p3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class HandlerData {
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());
}
out.close();
}catch(IOException e) {
e.printStackTrace();
}
}
public void readMethod(PipedInputStream in) {
try {
System.out.println("read:");
byte []byteArray=new byte[256];
int readLength=in.read(byteArray);
while(readLength!=-1) {
String newData=new String(byteArray,0,readLength);
System.out.println(newData);
readLength=in.read(byteArray);
}
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
主函数
package p3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Run1 {
public static void main(String[] args) throws IOException {
HandlerData handlerData=new HandlerData();
PipedInputStream in=new PipedInputStream();
PipedOutputStream out=new PipedOutputStream();
out.connect(in);
ThreadRead threadRead=new ThreadRead(handlerData, in);
ThreadWrite threadWrite=new ThreadWrite(handlerData, out);
threadRead.start();
threadWrite.start();
}
}
使用代码inputStream.connect(outputStream)或outputStream.connect(inputStream)的作用使两个Stream之间产生通信链接,这样才可以使数据进行输出与输入。
在此实验中,首先是读取线程new ThreadRead(inputStream)启动,由于当时没有数据被写入,所以线程阻塞在int read
Length=in.read(byteArray),直到有数据被写入,才能继续往下运行。
3.1.4通过管道进行线程间通信:字符流
管道中还可以传递字符流
将PipedInputStream换为PipedWriter
将PipedOutputStream换为PipedReader
3.2 方法join的使用
在很多情况下,主线程创建并启动子线程,如果子线程要进行大量的耗时运算,主线程往往早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后在结束,就要用到join()方法了,方法join()的作用是等待线程对象销毁。
3.2.1 join的运用
package p3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Run2 {
public static void main(String[] args) throws IOException, InterruptedException {
MyThread1 myThread1=new MyThread1();
myThread1.start();
myThread1.join();
System.out.println("在Thread执行完后再执行");}}
3.2.2 join方法
在join过程中,如果当前线程对象被中断,则当前线程出现异常。
方法join(long)设定等待的时间,方法join(long)的功能是使用wait(long)方法来实现的,所以join(long)有释放锁的特点。
package p3;
public class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB=threadB;
}
public void run() {
try {
synchronized (threadB) {
System.out.println("begin A ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end A ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
package p3;
import java.io.IOException;
public class ThreadB extends Thread{
synchronized public void run() {
try {
System.out.println("begin B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
}catch(Exception e) {
e.printStackTrace();
}
}
}
package p3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Run3 {
public static void main(String[] args) throws IOException, InterruptedException {
ThreadB threadB=new ThreadB();
ThreadA threadA=new ThreadA(threadB);
threadA.start();
threadB.start();
threadB.join(2000);
System.out.println("main end");
}
}
多次运行,结果不一样
第一种情况
运行顺序:
1)b.join(2000)方法先抢到B锁,然后将B锁进行释放
2)ThreadA抢到锁,打印begin A并且sleep(5000)
3)ThreadA打印end A,并释放锁
4)这是join(2000)和ThreadB争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁并打印main end
5)ThreadB抢到锁打印ThreadB begin
6)5秒之后再打印ThreadB end
第二种情况
运行顺序:
1)b.join(2000)方法先抢到B锁,然后将B锁进行释放
2)ThreadB抢到锁,并打印begin B,然后sleep(5000)
3)ThreadB释放锁,并打印end B
4)ThreadA与join(2000)争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁并打印main end
5)ThreadA抢到锁打印 begin A
6)ThreadA释放锁打印 end A
3.3 类ThreadLocal的使用
类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻为全局存放数据的盒子,盒子中可以存储每个线程的私有数据。
ThreadLocal的线程变量拥有隔离性,拥有get方法与set方法,当线程中没有set值的时候,get到的值为null。
package p3;
public class Thread1 extends Thread{
public void run() {
try {
for(int i=0;i<100;i++) {
Tools.t1.set("Thread1 set "+(i+1));
System.out.println("Thread1 get Value="+Tools.t1.get());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package p3;
public class Thread2 extends Thread{
public void run() {
try {
for(int i=0;i<100;i++) {
Tools.t1.set("Thread2 set "+(i+1));
System.out.println("Thread2 get Value="+Tools.t1.get());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package p3;
public class Tools {
public static ThreadLocal t1=new ThreadLocal();
}
package p3;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Run4 {
public static void main(String[] args) throws IOException, InterruptedException {
Thread1 thread1=new Thread1();
Thread2 thread2=new Thread2();
thread1.start();
thread2.start();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/eea09b830a1feffba75417e775dd423b.png)
为了解决get()一开始放回null值的问题,可以写一个类继承ThreadLocal,重写initialValue方法。
public class ThreadLocalExt extends ThreadLocal{
protected Object initialValue() {
return "hello";
}
}
3.4类InheritableThreadLocal
使用类InheritableThreadLocal在子线程中取得父线程继承下来的值,可以通过重写childValue方法来修改继承下来的值,如果子线程取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。
class InheritableThreadLocalExt extends InheritableThreadLocal{
protected Object initialValue() {
return "hello";
}
protected Object childValue() {
return "parentValue";
}
}