多线程

一、进程、线程的区别

1、进程

          进程是运行中的程序,是系统进行资源分配和调度的基本单位。

          它有以下几个特征:

         <1>独立性:具有私有的地址空间

         <2>动态性:活动的指令集合,而程序是静态的

         <3>并发性:多个进程跑在单个处理器上

2、线程

           一个进程中包含了多个顺序执行流,每个顺序执行流都是一个线程。

          多个线程共享同一个进程的虚拟空间,共享的环境包括:进程代码段、进程的公有数据


二、创建进程的三种方式

1、继承Thread

public class FirstThread extends Thread{	
    private int i=0;
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for(;i<100;i++)
		     System.out.println(getName()+" "+i);//可以直接调用getName()获取线程名
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
        for(int i=0;i<100;i++)
        {
        	System.out.println(Thread.currentThread().getName()+" "+i);
        	if(i==20)
        	{
        		new FirstThread().start();//默认名为Thread-0
        		new FirstThread().start();//默认名为Thread-1
        	}
        }
	}
}

2、实现Runnable接口

public class SecondThread implements Runnable{
    private int i=0;
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		for(;i<100;i++)
		     System.out.println(Thread.currentThread().getName()+" "+i);//只能通过这种方式获取当前线程
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
        for(int i=0;i<100;i++)
        {
        	System.out.println(Thread.currentThread().getName()+" "+i);
        	if(i==20)
        	{
        		SecondThread sT=new SecondThread();
        		new Thread(sT,"线程1").start();
        		new Thread(sT,"线程2").start();
        	}
        }
	}
}

3、实现Callable接口,重写call()方法

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,运行callable()可以拿到一个Future对象,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.FutureTask;  
  
public class CallableThreadTest implements Callable<Integer>  
{  
  
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }  
  
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
  
}  

创建线程的三种方式的对比:

采用实现Runnable、Callable接口的方式创见多线程时,优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。



三、线程的生命周期



四、控制线程

1、join线程

      当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程完成为止。

      join()方法可用于将大问题划分为小问题,每个小问题分配一个线程,当所有的小问题得到处理后,再调用主线程来进一步操作。

public class JoinThread extends Thread{
	//提供一个有参数的构造器用于设置该线程的名字
	public JoinThread(String name)
	{
		super(name);
	}
	//重写run方法,定义线程执行体
	public void run()
	{
		for(int i=0;i<100;i++)
			  System.out.println(getName()+" "+i);
	}
	public static void main(String[] args) throws InterruptedException {
		// TODO 自动生成的方法存根
        //启动子线程
		new JoinThread("新线程").start();
		for(int i=0;i<100;i++)
		{
			if(i==20)
			{
				JoinThread jt=new JoinThread("被join的线程");
				jt.start();
//				main线程调用了jt线程的join方法,main线程必须等待jt执行完之后才向下执行
				jt.join();
			}
			 System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}

}

2、后台线程

        后台线程的任务是为其他线程提供服务,JVM的垃圾回收线程就是典型的后台线程

        后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡

public class DaemonThread extends Thread{
	public void run()
	{
		for(int i=0;i<1000;i++)
			  System.out.println(getName()+" "+i);
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		DaemonThread dThread=new DaemonThread();
		//将此线程设置为后台进程
		dThread.setDaemon(true);
		//启动后台进程
		dThread.start();
		for(int i=0;i<10;i++)
			  System.out.println(Thread.currentThread().getName()+" "+i);
		//---------程序执行到此处,前台进程(main进程)结束---------
		//后台进程也随之结束
	}

}
执行之后发现后台进程无法执行到999,因为主线程跑10次之后会结束,所以后台线程也就被结束了。

3、线程睡眠sleep

      使用Thread提供的sleep()静态方法可以让当前正在执行的线程暂停一段时间进入阻塞状态

import java.util.Date;

public class TestSleep {

	public static void main(String[] args) throws InterruptedException {
		// TODO 自动生成的方法存根
		for(int i=0;i<10;i++)
		{
			System.out.println("当前的时间是: "+new Date());
			Thread.sleep(1000);
		}
	}

}

4、线程让步yield

           yield只是让当前线程暂停一下,将该线程转入就绪状态,让系统的线程调度器重新调度一次 

public class TestYield extends Thread{

	public TestYield()
	{}
	public TestYield(String name)
	{
		super(name);
	}
	public void run()
	{
		for(int i=0;i<50;i++)
		{
			  System.out.println(getName()+" "+i);
			  //当i=20时,使用yield方法让当前线程让步
			  if(i==20)
			  {
				  Thread.yield();
			  }
		}
	}
	public static void main(String[] args) {
		//启动两条并发进程
		TestYield ty1=new TestYield("高级");
		//将ty1设置为最高优先级
		ty1.setPriority(Thread.MAX_PRIORITY);
		ty1.start();
		
		TestYield ty2=new TestYield("低级");
		//将ty1设置为最低优先级
		ty2.setPriority(Thread.MIN_PRIORITY);
		ty2.start();
	}

}

sleep()和wait()的区别:

1. 这两个方法来自不同的类,sleep方法属于Thread,wait方法属于Object。

2. 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3. wait, notify和notifyAll只能在同步控制方法(synchronized)或者同步控制块里面使用,而sleep可以在任何地方使用。

4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

sleep()和yield()的区别:

1、sleep()给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会,而yield()只会给相同优先级或更高优先级的线程运行的机会。

2、线程执行sleep()后转入阻塞状态,而yield()使当前线程回到就绪状态,让OS重新执行线程调度。


 五、线程同步

    看一个银行取款的例子:

    <1>用户输入账号、密码,系统判断是否匹配

     <2>用户输入取款金额

     <3>系统判断账户余额是否大于取款金

     <4>如果余额大于取款金额,取款成功;如果余额小于取款金额,取款失败。

Account.class 

public class Account {
	//封装账户编号、账户余额
	private String accountNo;
	private double balance;
	public Account(){}
	public Account(String accountNo,double balance)
	{
		this.accountNo=accountNo;
		this.balance=balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	public void setBalance(double balance) {
		this.balance = balance;
	}
	
	//下面两个方法根据accoutNo来计算Account的hashCode和判断equals
	@Override
	public int hashCode() {
		return accountNo.hashCode();
	}
	@Override
	public boolean equals(Object obj) {//重写Object的equals方法,让其通过内容来判断是否相等
		if(obj!=null&&obj.getClass()==Account.class)
		{
			Account target=(Account)obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
DrawAmount.class

public class DrawThread extends Thread{
	//模拟用户账户
	private Account account;
	//当前取钱的钱数
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		super(name);
		this.account=account;
		this.drawAmount=drawAmount;
	}
	//当多条线程修改同一共享数据时,将涉及数据安全问题
	public void run()
	{
	if(account.getBalance()>=drawAmount)
			{
				//吐出钞票
				System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
				
				//人为强制进行线程切换
				try{
					Thread.sleep(1);
				}
				catch(InterruptedException ex){
					ex.printStackTrace();
				}
				
				//修改余额
				account.setBalance(account.getBalance()-drawAmount);
				System.out.println("\t余额为:"+account.getBalance());
			}
			else{
				System.out.println(getName()+"取钱失败!余额不足!");
			}
		}

}
TestDraw.java

public class TestDraw {

	public static void main(String[] args) {
		//创建账户
		Account account=new Account("1234567",1000);
		//模拟两个线程对同一账户取钱
		new DrawThread("甲", account, 800).start();
		new DrawThread("乙", account, 800).start();
	}
}


多次运行程序(不进行人为的线程切换),会出现以下由于线程同步导致的结果:


1、使用同步代码块解决

synchronized(obj)
{
   ......
   //此处的代码就是同步代码块
}
obj是同步监视器,即线程开始执行之前,必须获得对同步监视器的锁定;当线程执行完之后,会自然释放对该同步监视器的锁定

修改上述代码如下:

synchronized (account) {//加锁
			if(account.getBalance()>=drawAmount)
			{
				//吐出钞票
				System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
				
				//人为强制进行线程切换
				try{
					Thread.sleep(1);
				}
				catch(InterruptedException ex){
					ex.printStackTrace();
				}
				
				//修改余额
				account.setBalance(account.getBalance()-drawAmount);
				System.out.println("\t余额为:"+account.getBalance());
			}
			else{
				System.out.println(getName()+"取钱失败!余额不足!");
			}
		}

2、同步方法

        同步方法就是使用synchronized关键字来修饰某个方法,无需指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。

通过使用同步方法可以非常方便地将某类变成线程安全的类

        改写上面的例子如下:

        Account.class

public class Account {
	
	//封装账户编号、账户余额
	private String accountNo;
	private double balance;
	public Account(){}
	public Account(String accountNo,double balance)
	{
		this.accountNo=accountNo;
		this.balance=balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	public double getBalance() {
		return balance;
	}
	//提供一个线程安全方法来完成取钱操作
	public synchronized void draw(double drawAmount)
	{
		if(balance>=drawAmount)
		{
			//吐出钞票
			System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
			
			//人为强制进行线程切换
			try{
				Thread.sleep(1);
			}
			catch(InterruptedException ex){
				ex.printStackTrace();
			}
			
			//修改余额
			balance-=drawAmount;
			System.out.println("\t余额为:"+balance);
		}
		else{
			System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足!");
		}
	}
	
	//下面两个方法根据accoutNo来计算Account的hashCode和判断equals
	@Override
	public int hashCode() {
		return accountNo.hashCode();
	}
	@Override
	public boolean equals(Object obj) {//重写Object的equals方法,让其通过内容来判断是否相等
		if(obj!=null&&obj.getClass()==Account.class)
		{
			Account target=(Account)obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
DrawThread.class

        

public class DrawThread extends Thread{
	//模拟用户账户
	private Account account;
	//当前取钱的钱数
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		super(name);
		this.account=account;
		this.drawAmount=drawAmount;
	}
	public void run()
	{
		account.draw(drawAmount);
	}
}
同步方法的同步监视器是this,因此对于同一个Account账户而言,任意时刻只能有一条线程获得对Account对象的锁定,然后进入draw方法进行取钱操作

注意:

        线程安全的类是以降低程序运行的效率作为代价的,不要对线程安全的类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法进行同步。

3、同步锁(lock)

使用Lock时显示使用Lock对象作为同步锁,需要显式释放;而使用同步方式时系统隐式使用当前对象作为作为同步监视器

ReentrantLock锁具有重入性,即线程可以对它已经加锁的ReentrantLock锁再次加锁

 private final ReentrantLock lock=new ReentrantLock();

public  void draw(double drawAmount)
		{
			//对同步锁进行加锁
			lock.lock();
			try{
			if(balance>=drawAmount)
			{
				//吐出钞票
				System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
				
				//人为强制进行线程切换
				try{
					Thread.sleep(1);
				}
				catch(InterruptedException ex){
					ex.printStackTrace();
				}
				
				//修改余额
				balance-=drawAmount;
				System.out.println("\t余额为:"+balance);
			}
			else{
				System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足!");
			}
			}
			finally{
				lock.unlock();
			}
		}


synchronized和Lock的区别

1.synchronized更容易造成死锁

  线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断,容易造成死锁;如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

2.ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。 
3.在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态
4、lock能完成synchronized的所有功能

扩展: 

1.synchronized与static synchronized 的区别
      synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。实际上,在类中某方法或某代码块中有 synchronized,那么在生成一个该类实例后,改类也就有一个监视快,放置线程并发访问改实例synchronized保护快,而static synchronized则是所有该类的实例公用一个监视快了,也也就是两个的区别了,也就是synchronized相当于 this.synchronized,而
static synchronized相当于Something.synchronized.
      一个日本作者-结成浩的《java多线程设计模式》有这样的一个列子:
      pulbic class Something(){
         public synchronized void isSyncA(){}
         public synchronized void isSyncB(){}
         public static synchronized void cSyncA(){}
         public static synchronized void cSyncB(){}
     }
   那么,加入有Something类的两个实例a与b,那么下列组方法何以被1个以上线程同时访问呢
   a.   x.isSyncA()与x.isSyncB() 
   b.   x.isSyncA()与y.isSyncA()
   c.   x.cSyncA()与y.cSyncB()
   d.   x.isSyncA()与Something.cSyncA()
    这里,很清楚的可以判断:
   a,都是对同一个实例的synchronized域访问,因此不能被同时访问
   b,是针对不同实例的,因此可以同时被访问
   c,因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与   Something.isSyncB()了,因此不能被同时访问。
   那么,第d呢?,书上的 答案是可以被同时访问的,答案理由是synchronzied的是实例方法与synchronzied的类方法由于锁定(lock)不同的原因。
   个人分析也就是synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。目前还不是分清楚java内部设计synchronzied是怎么样实现的。


    结论:A: synchronized static是某个类的范围,synchronized static cSync{}防止多个线程同时访问这个    类中的synchronized static 方法。它可以对类的所有对象实例起作用。
   
               B: synchronized 是某实例的范围,synchronized isSync(){}防止多个线程同时访问这个实例中的synchronized 方法。

     2.synchronized方法与synchronized代码快的区别
      synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是
 synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。


六、线程通信

           程序通常无法准确控制线程的轮换执行,我们可以通过一些机制来保证线程协调运行。

      举例说明:

            假设有两条线程,分别代表存款者和取款者,系统要求存款者和取款者不断重复存款取款操作,且每当存款者将钱存入后取款者立即取出。不允许存款者连续两次存钱,也不允许取款者连续两次取钱

1、使用synchronized+Object类提供的wait()、notify()和notifyAll()三个方法

      synchronized控制同步,三个方法控制协调

      这三个方法必须由同步监视器对象来调用

      <1>对于使用synchronized修饰的同步方法,因为默认的实例this就是同步监视器,所以可以在同步方法中直接调用

      <2>对于使用synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象来调用这三个方法

Account.class

public class Account {
	private String accountNo;
	private double balance;
	//表示账户中是否有存款
	private boolean flag=false;
	
	public Account(){}
	public Account(String accountNo,double balance)
	{
		this.accountNo=accountNo;
		this.balance=balance;
	}
	public double getBalance()
	{
		return this.balance;
	}

	public synchronized void draw(double drawAmount)//取款
	{
		try{
		if(!flag)
		{
			wait();
		}
		else {
			//取钱
			System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
			balance-=drawAmount;
			System.out.println("账户余额为:"+balance);
            //将标识设置为false
			flag=false;
			//唤醒其他线程
			notifyAll();
		}
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
	}
	public synchronized void deposit(double depositAmount)//存款
	{
		try{
			if(flag)
			{
				wait();
			}
			else {
				//存款
				System.out.println(Thread.currentThread().getName()+"存款:"+depositAmount);
				balance+=depositAmount;
				System.out.println("账户余额为:"+balance);
				//设置flag
				flag=true;
				//唤醒其他线程
				notifyAll();
			}
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
	}
}
DepositThread.class

public class DepositThread extends Thread{
	   //模拟用户账户
		private Account account;
		//当前的存钱数
		private double depositAmount;
		public DepositThread(String name,Account account,double depositAmount)
		{
			super(name);
			this.account=account;
			this.depositAmount=depositAmount;
		}
		//重复100次取钱操作
		public void run()
		{
			for(int i=0;i<100;i++)
			{
				account.deposit(depositAmount);
			}
		}
}

DrawThread.class

public class DrawThread extends Thread{
	//模拟用户账户
	private Account account;
	//当前的取钱数
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		super(name);
		this.account=account;
		this.drawAmount=drawAmount;
	}
	//重复100次取钱操作
	public void run()
	{
		for(int i=0;i<100;i++)
		{
			account.draw(drawAmount);
		}
	}
}


TestDraw.class

public class TestDraw {

	public static void main(String[] args) {
		// 创建一个用户
		Account account=new Account("1234567",0);
		new DrawThread("取钱者", account, 800).start();
		new DepositThread("存款者", account, 800).start();
	}
}

运行结果:

        存取款者会交替运行


2、使用Lock+Condition(条件变量)提供的await()、signal()和signalAll()三个方法

public class Account {
	//显示定义Lock对象
	private final Lock lock=new ReentrantLock();
	//获得指定Lock对象对应的条件变量
	private final Condition cond=lock.newCondition();
	
	private String accountNo;
	private double balance;
	//表示账户中是否有存款
	private boolean flag=false;
	
	public Account(){}
	public Account(String accountNo,double balance)
	{
		this.accountNo=accountNo;
		this.balance=balance;
	}
	public double getBalance()
	{
		return this.balance;
	}

	public void draw(double drawAmount)//取款
	{
		lock.lock();
		try{
		if(!flag)
		{
			wait();
		}
		else {
			//取钱
			System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
			balance-=drawAmount;
			System.out.println("账户余额为:"+balance);
            //将标识设置为false
			flag=false;
			//唤醒其他线程
			cond.signalAll();
		}
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
		finally{
			lock.unlock();
		}
	}
	public  void deposit(double depositAmount)//存款
	{
		lock.lock();
		try{
			if(flag)
			{
				cond.await();
			}
			else {
				//存款
				System.out.println(Thread.currentThread().getName()+"存款:"+depositAmount);
				balance+=depositAmount;
				System.out.println("账户余额为:"+balance);
				//设置flag
				flag=true;
				//唤醒其他线程
				cond.signal();
			}
		}
		catch(InterruptedException ex)
		{
			ex.printStackTrace();
		}
		finally{
			lock.unlock();
		}
	}
}

3、使用管道流

        前面的两种方式与其称为线程之间的通信,还不如称为线程之间协调运行的控制策略。如果需要在两条线程之间进行更多的交互,可以考虑使用管道流进行通信


public class WriterThread extends Thread {
	String[] books=new String[]{
		"AAAAAAAAAAAA",
		"BBBBBBBBBBBB",
		"CCCCCCCCCCCC",
		"DDDDDDDDDDDD"
	};
	private PipedWriter pw;
	public WriterThread(){}
	public WriterThread(PipedWriter pw)
	{
		this.pw=pw;
	}
	public void run()
	{
		try{
			//循环一百次,向管道输出流写入100个字符串
			for(int i=0;i<100;i++)
			{
				pw.write(books[i%4]+"\n");
			}
			
		}
		catch(IOException ex)
		{
			ex.printStackTrace();
		}
		//使用finally关闭管道流
		finally{
			try{
				if(pw!=null)
				{
					pw.close();
				}
			}
			catch(IOException ex)
			{
				ex.printStackTrace();
			}
		}
	}
}

public class ReaderThread extends Thread{
	private PipedReader pr;
	//用于包装管道流的BufferReader对象
	private BufferedReader br;
	public ReaderThread(){}
	public ReaderThread(PipedReader pr)
	{
		this.pr=pr;
		this.br=new BufferedReader(pr);
	}
	
	public void run()
	{
		String buf=null;
		try{
			//逐行读取管道输入流的内容
			while((buf=br.readLine())!=null)
			{
				System.out.println(buf);
			}
		}
		catch(IOException ex)
		{
			ex.printStackTrace();
		}
		//使用finally关闭输入流
		finally {
			try{
				if(br!=null)
				{
					br.close();
				}
			}
			catch(IOException ex)
			{
				ex.printStackTrace();
			}
		}
	}
}

public class PipedCommunicationTest {
	public static void main(String[] args) {
		PipedWriter pw=null;
		PipedReader pr=null;
		
		try{
			//分别创建两个独立的管道输出流、输入流
			pw=new PipedWriter();
			pr=new PipedReader();
			//连接管道输入流、输出流
			pw.connect(pr);
			//将连接好的管道流分别传入两个线程,就可以让两个线程通过管道流通信
			new WriterThread(pw).start();
			new ReaderThread(pr).start();
		}
		catch(IOException ex)
		{
			ex.printStackTrace();
		}
	}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值