线程间通信
等待/通知机制
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入”预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁,如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException
方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。
wait使线程停止运行,而notify使停止的线程继续运行
synchronized(lock){
System.out.println("开始 wait time="+System.currentTimeMillis());
lock.wait();
System.out.println("结束 wait time="+System.currentTimeMillis());
}
...
synchronized(lock){
System.out.println("开始 notify time="+System.currentTimeMillis());
lock.notify();
System.out.println("结束 notify time="+System.currentTimeMillis());
}
释放锁的情况:
- 1)执行完同步代码块就会释放对象的锁
- 2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
- 3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒
方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒
生产者/消费者模式实现
//生产者
public class P{
private String lock;
public P(String lock){
super();
this.lock = lock;
}
public void setValue(){
try{
synchronized(lock){
while(!ValueObject.value.equals("")){
System.out.println("生产者"+Thread.currentThread().getName+" WAITING了");
lock.wait();
}
System.out.println("生产者"+Thread.currentThread().getName()+" RUNNABLE了");
String value = System.currentTimeMillis()+"_"+System.nanoTime();
ValueObject.value = value;
lock.notify();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
//消费者
public class C{
private String lock;
public C(String lock){
super();
this.lock = lock;
}
public void getValue(){
try{
synchronized(lock){
while(ValueObject.value.equals("")){
System.out.println("消费者"+Thread.currentThread().getName+" WAITING了");
lock.wait();
}
System.out.println("消费者"+Thread.currentThread().getName()+" RUNNABLE");
ValueObject.value = "";
lock.notify();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
//线程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();
}
}
}
//线程C
public class ThreadC extends Thread{
private C c;
public ThreadP(C c){
super();
this.c = c;
}
@Override
public void run(){
while(true){
c.setValue();
}
}
}
//执行
public class Run{
public static void main(String[] args) throws InterruptedException{
String lock = new String("");
P p = new P(lock);
C c = new C(lock);
ThreadP[] pThread = new ThreadP[2];
ThreadC[] cThread = new ThreadC[2];
for(int i = 0;i<2;i++){
pThread[i] = new ThreadP(p);
pThread[i].setName("生产者"+(i+1));
cThread[i] = new ThreadC(c);
pThread[i].setName("消费者"+(i+1));
pThread[i].start();
cThread[i].start();
}
Thread.sleep(5000);
Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
Thread.currentThread().getThreadGroup().enumerate(threadArray);
for(int i =0;i<threadArray.length;i++){
System.out.println(threadArray[i].getName()+""+threadArray[i].getState());
}
}
}
多生产与多消费导致假死:notify唤醒的不是异类而是同类,比如”生产者”唤醒”生产者”或”消费者”唤醒”消费者”,就会导致所有的线程都不能继续运行下去,都呈WAITING状态
解决:用notifyAll()代替notify()
通过管道进行线程间通信:字节流
管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输出管道中读取数据。从而实现不同线程间的通信
在Java的JDK中提供了4个类来使线程间可以进行通信:
- 1)PipedInputStream和PipedOutputStream
2)PipedReader和PipedWriter
//WriteData类 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(); } } } //ReadData类 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(); } } } //Write线程 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); } } //Read线程 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); } } //Run类 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.connnect(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(); } } }
使用inputStream.connect(outputStream)或outputStream.connect(inputStream)的作用使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入
管道中还可以传递字符流,只要将PipedInputStream换成PipedReader,PipedOutputStream换成PipedWriter
多线程join的使用
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。如果主线程想等到子线程执行完成后再结束,就要用到join()方法,
join()方法的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码
方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是”对象监视器”原理做为同步
在join过程中,如果当前线程对象被中断,则当前线程出现异常(join()和interrupt()彼此遇到就会出现异常)
join(long)中的参数是设定等待的时间
join(long)与sleep(long)的区别:
join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
而Thread.sleep(long)方法却不释放锁
类ThreadLocal的使用
变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量,如果想实现每一个线程都有自己的共享变量可以使用类ThreadLocal
类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据
public class ThreadLocalExt extends ThreadLocal{
//覆盖initialValue()方法具有初始值
@Override
protected Object initialValue(){
return new Date().getTime();
}
}
public class Tools{
public static ThreadLocalExt t1 = new ThreadLocalExt();
}
//线程A
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();
}
}
}
//Run类
public class Run(){
public static void main(String[] args){
try{
for(int i = 0;i<10;i++){
System.out.print(" 在Main线程中取值="+Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}