java多线程编程核心技术(学习笔记三)

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();
		
	 }
}


为了解决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";
	 }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值