Java之多线程

1.创建线程

Java的多线程机制提供了两种方式实现多线程编程:一种是通过继承java.long.Thread类来实现,另一种是通过实现Runable接口实现

1)继承Thread类创建线程

Thread类已经具备了运行多线程所需要的资源,用户只需要重载该类的run()方法,把需要使用多线程运行的代码放入该方法。这样这些代码就可以和其他线程“同时”存在,创建线程对象并用该对象调用start()方法则线程开始运行。

继承Thread类实现多线程示例:

package LianXi;


public class XianCheng_LX extends Thread {
    //定义参数count记录线程的执行,number作为线程计数器
int count=1,number;
//定义构造函数,参数为int型,标示新建的线程
public XianCheng_LX(int num){
number=num;
System.out.println("创建线程"+number);
}
//run()方法内的代码是线程执行的内容,这些代码在执行过程中可能被打断执行
public void run(){
while(true){
System.out.println("线程"+number+": 计数"+count);
if(++count==10) return;
}
}
public static void main(String[] args) {
for(int i=0;i<3;i++)
         new XianCheng_LX(i+1).start();
}
}

运行结果:

创建线程1
创建线程2
线程1: 计数1
线程2: 计数1
创建线程3
线程2: 计数2
线程2: 计数3
线程2: 计数4
线程2: 计数5
线程2: 计数6
线程1: 计数2
线程2: 计数7
线程1: 计数3
线程3: 计数1
线程2: 计数8
线程3: 计数2
线程1: 计数4
线程3: 计数3
线程2: 计数9
线程3: 计数4
线程1: 计数5
线程1: 计数6
线程3: 计数5
线程1: 计数7
线程3: 计数6
线程1: 计数8
线程3: 计数7
线程1: 计数9
线程3: 计数8
线程3: 计数9


多线程程序可以保证每个线程都会得到执行,只是执行顺序无法预料

在通过继承Thread类编写多线程程序时,需要用到两个方法run()和start()

(1)程序员必须覆盖run()方法,把需要多线程执行的代码放在函数体内,如果没有覆盖该方法,程序可以顺利通过编译,但是即使调用了start()方法,因为没有覆盖run()方法

,程序无法找到多线程执行的代码,所以实际上相当于单线程程序。

多线程需要调用start()方法。该方法会初始化一些和多线程有关的资源,而后会自动调用run()方法


2)实现Runable接口创建线程

因为Java不支持多继承,所以如果用户的类已经继承了一个类,而又需要多线程机制的支持,此时继承Thread类就不现实了,所以Runnable接口在这种情况下很实用。

Runnable接口有唯一一个方法run()。如果运行通过实现Runnable接口的多线程程序,则需要借助Thread类,因为Runnable接口没有提供任何东西支持多线程,必须借助

Thread类的框架实现多线程,即通过类Thread的构造函数public Thread(Runnable target)来实现

实现Runnable接口的多线程示例:

package LianXi;
public class XianCheng_LX implements Runnable { 
//定义参数count记录线程的执行,number作为线程计数器
int count=1,number;
//定义构造函数,参数为int型,标识新建的线程
public XianCheng_LX(int num){
number=num;
System.out.println("创建线程"+number);
}
//run()方法内的代码是线程执行的内容,这些代码在执行过程中可能被打断执行
public void run(){
while(true){
System.out.println("线程"+number+":计数"+count);
if(++count==10) return;
}
}
public static void main(String[] args) {
//使用类Thread的构造函数Thread(Runnable runObject)
for(int i=0;i<3;i++){
new Thread(new XianCheng_LX(i+1)).start();
}
}
}

注意:使用Runable接口实现多线程时,在启动线程时需要Thread类的帮助。


2.线程的状态

Java规范中只定义了线程的4种状态:新建状态、可运行状态、阻塞状态和死亡状态。

这里把可运行状态分解为就绪状态和运行状态。即线程包括5个状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。

(1)新建状态:线程对象(通过new关键字)已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍然不能做任何事情,此时线程处在新建状态,程序

  没有运行线程中的代码。如果线程要运行,需要处于就绪状态

(2)就绪状态:一个线程一旦调用了start()方法,该线程就处于就绪状态。此时线程等待CPU时间片,一旦获得CPU周期线程就可以执行。这种状态下的任何时刻线程是否

执行完全取决于系统的调度程序

(3)运行状态:一旦处于就绪状态的线程获得CPU周期,就处于运行状态,执行多线程代码部分的运算。线程一旦运行,只是在CPU周期内获得执行权利,而一旦CPU的

时间片用完,操作系统会给其它线程运行的机会,而剥夺当前线程的执行

(4)阻塞状态:该状态下线程无法执行,必须满足一定条件后方可执行,如果线程处于阻塞状态,JVM的调度机不会为其分配CPU周期。而一旦线程满足一定的条件就解除

阻塞,线程处于就绪状态,此时就获得了被执行的机会。当发生以下情况时会使得线程进入阻塞状态:

     1》线程在等待一个输入、输出操作,该操作完成前不会返回其调用者

     2》线程调用了wait()方法或者sleep()方法。

     3》线程需要满足某种条件才可以继续执行

如果线程处于阻塞状态,其他线程就可以被CPU执行。而当一个线程解除了阻塞状态,如线程等待输入、输出操作而该操作已经完成,线程调度机会检查改线程的优先权,如果优先权高于当前的运行线程,该线程将抢占当前线程的资源并开始运行

(5)死亡状态:线程一旦退出run()方法就处于死亡状态。

3.线程的优先级

在java中虽然定义了设置线程优先级高低的方法,但是优先级低并不意味着在不同优先级的线程中不会被执行,优先级低只说明该线程被执行的概率小,同理优先级高的线程

获得CPU周期的概率就大

通过Thread类的setPriority()方法设置线程的优先级。该方法的参数为int型。

java提供了3个优先级别,都为Thread类的常量,从高到低依次是Thread.MAX_PRIORITY(10)、Thread.NORM_PRIORITY(5)、Thread.MIN_PRIORITY(1)。

设置线程的优先级实例:

package LianXi;
public class XianCheng_LX extends Thread {
int count=1,number;
public XianCheng_LX(int num,int priority){
setPriority( priority);
number=num;
System.out.println("创建线程:"+number);
}
public void run(){
while(true){
System.out.println("线程"+number+": 计数"+count);
if(++count==10) return;
}
}
public static void main(String[] args) {
new XianCheng_LX(3,Thread.MAX_PRIORITY).start();
for(int i=0;i<2;i++)
new XianCheng_LX(i+1,1).start();;
}
}

运行结果:

创建线程:3
创建线程:1
线程3: 计数1
创建线程:2
线程3: 计数2
线程3: 计数3
线程3: 计数4
线程3: 计数5
线程3: 计数6
线程3: 计数7
线程3: 计数8
线程3: 计数9
线程1: 计数1
线程1: 计数2
线程1: 计数3
线程2: 计数1
线程1: 计数4
线程2: 计数2
线程1: 计数5
线程2: 计数3
线程1: 计数6
线程1: 计数7
线程1: 计数8
线程2: 计数4
线程1: 计数9
线程2: 计数5
线程2: 计数6
线程2: 计数7
线程2: 计数8
线程2: 计数9


4.线程的同步

1)synchronized关键字

在设计多线程模式中,解决线程冲突问题都是采用synchronized关键字来实现的。这意味着在给定时刻只允许一个线程访问共享资源。通常是在代码前加上一条锁语句实现

的,这就保证了在一段时间内只有一个线程运行这段代码。如果另一个线程需要访问这段共享资源,必须等待当前的线程释放锁。可见锁语句产生了一种互斥的效果,所有

常常称锁为“互斥量”

要控制对共享资源的访问,首先要把它封装进一个类,即编写一个方法来访问共享资源。为了保证对象在调用该方法访问资源时实现互斥访问,必须提供保证机制,保证顺序

访问共享资源。一般来说,类中的数据成员被声明为私有的,只有通过方法来访问这些数据,所以可以把方法标记为synchronized来防止资源冲突。下面是声明synchronized

的方法。

同步控制方法:

/在类中实现共享资源的锁定方式
public class SynDefi {
 //synchronized 修饰方法,它取得的锁交给调用该方法的对象
public synchronized  void method1(){
//方法体
}
//在方法体中实现同步控制块,该方法的锁交给类SynDefi的某个对象(this)
public void method2(){
synchronized(this){
//同步控制块代码
}
}
//同步控制块
//synchronized用于对象引用,该方法的锁交给该引用所指的对象
public void method3(Object someObj){
synchronized(someObj){
//同步控制块代码
}
}
}

由此可以看出,关键字synchronized即可以修饰方法,也可以作为方法内的语句,实现代码被访问的安全性,但是这并不意味着同一个时刻只能有一个线程访问被synchronized

关键字保护的代码。因为对于synchronized函数而言,关键字synchronized锁定的不是代码或方法本身,而是调用该方法的对象。

2)同步控制方法

在方法前增加一个synchronized关键字,表示如果一个线程调用该方法,必须首先获得方法所在类的对象的锁,执行完之后释放锁。下一个线程在访问该方法前,先获得锁,然

后再执行代码,这样就实现了对共享资源的顺序访问,保证了多线程的安全性。

调用synchronized修饰的方法的对象获得一个锁,该锁属于synchronized修饰的方法所在类的对象的一部分(每个对象有一个唯一的锁,如果该类中有多个synchronized修饰的方法,它们也共用一把锁)

synchronized同步控制机制示例:

//定义一个类,继承自线程Thread
public class FooOne extends Thread {
 private int val;
 //该String变量为线程标识
 public FooOne(int v){
val=v;
 }
 //使用snchronized修饰方法priintVal,使得调用该方法的对象获得锁,实现互斥访问
 //该方法实现无限循环地输出一个int型变量
 public synchronized void printVal(int v){
while(true)
System.out.println(v);
 }
 //定义多线程要执行的内容,即run()方法体内容
   public void run(){
  printVal(val);
   }
}

//定义一个类FooTwo,继承自Thread类
public class FooTwo extends Thread {
  private FooOne sameFoo;
  //该类的构造函数传入一个类FooOne的对象引用
  public FooTwo(FooOne f){
 sameFoo=f;
  }
  //多线程部分是调用FooOne的对象的printVal方法,方法参数是2
  public void run(){
 sameFoo.printVal(2);
  }
}

//定义程序主类
public class Test {  

public static void main(String[] args) { 
//创建线程类FooOne的对象并启动线程
FooOne f1 = new FooOne(1);
f1.start();
   //创建线程类FooTwo的对象并启动该线程
FooTwo f2=new FooTwo(f1);
f2.start();
   //创建类FooOne的对象,构造函数参数为3,并启动线程
FooOne f3=new FooOne(3);
f3.start();
}
}

代码分析:

首先创建类FooOne的对象f1,构造函数传入参数为1,此时变量val的值1。通过f1调用Thread类的start()函数启动线程,程序执行类FooOne中方法run()的内容,即调用方法

printVal()。因为synchronized关键字修饰方法printVal(),我们把该线程称为线程A,则线程A获得FooOne对象f1的锁,而后打印数值1.因为是无限循环,run()方法永远不会退出,

线程A将不会释放对象f1的锁。

接着程序创建了类FooTwo的对象f2,此时的构造函数的参数为对象引用f1,启动该线程,不妨设该线程为B,该线程调用同一个对象f1的synchronized修饰的方法,且传入的参数

为2.显然调用snchronized修饰的方法,需要事先获得该方法所在对象的锁,即需要对象f1的锁才可以访问该方法。但是此时该锁只有等到线程A释放后才能获得,而线程A进入

一个无限循环,显然不会释放该锁。因此线程B处于阻塞状态,无法调用对象f1的方法printVal(),因此也永远不会看到线程A和线程B交替出现的情景。

接着创造类FooOne的一个新对象f2,构造函数传入参数3,启动该线程,不妨设该线程为C,此时FooOne的run()方法被调用,接着调用synchronized修饰的方法printVal().

此时线程试图获得对象f2的锁,而此时无论线程B是否一直持有对象f1的锁,线程C肯定可以获得对象f2的锁,因为此时printVal是被一个新对象f2调用,显然f2的锁和f1的锁

不同,所以线程C可以获得对象f2的锁。因此从执行结果上只有线程A和线程C在交替执行的输出过程,却永远不会出现线程B的输出。

注意:

同步机制锁定的是对象,不是代码或函数。只要有不同的对象就有不同的锁。所以一定要理解什么是“同一个对象”,一个类创建了多次,且对象赋予不同的对象引用,这些对象

各持有自己的锁,是不同的对象(不同的存储空间)。所以函数和代码部分被声明为synchronized并不意味着同一时刻只能有一个线程执行同步资源。

synchronized修饰控制类成员变量(共享资源)访问的方法,由于每个类实例有唯一一把锁,使得调用synchronized修饰的方法的线程都必须首先获得该方法类实例的锁才可以

执行,执行期间独占该锁,其他线程被阻塞,直到获得锁的线程执行完毕释放锁后,被阻塞的线程才可以获得锁,进入执行状态。

除了类实例外,每个类也对应一把锁,这样就可以把类的每个静态成员函数声明为synchronized,来控制线程对类的静态成员变量的访问。代码如下所示:

public class SynStaticMethod implements Runnable{
 private SomeObj obj;
 public synchronized static void method1(){
synchronized(obj){
//共享资源代码
}
 }
}

3)同步控制块

实际中会遇到这样一种情况:两个函数共享公共资源,为了使资源得到保护,必须实现资源访问的同步控制,尤其是static方法和非static方法共享资源的情况。此时可以使用

同步控制块来解决这个问题。

同步代码块示例:

public class SynControlBlock implements Runnable{
 private SomeObj obj;
 public  static void method1(){
synchronized(obj){
//共享资源代码
}
 }
 public void method2(){
synchronized(obj){
//共享资源代码
}
 }
}

代码说明:

当访问method1()和method2()时,访问这些方法的线程首先获得对象obj的锁,而后执行共享资源代码。如果此时有线程B要访问method2()方法,则必须首先获得对象obj的锁

所以线程B不能访问方法method2()

也可以同步控制一个本地变量,由于只能锁定对象,所以本地变量必须是个对象。代码示例如下:

public class SynControlBlock implements Runnable{
 private String synString=new String("");
 public  static void method1(){
synchronized(synString){
//共享资源代码
}
 }
 public void method2(){
synchronized(synString){
//共享资源代码
}
 }
}

代码说明:该程序声明了一个本地变量,使用该本地变量同样可以实现代码的同步控制。

4)线程的控制

(1)启动线程

无论是通过继承Thread类实现多线程还是通过实现Runnable接口实现多线程,如果要启动线程都需要Thread类的start()方法。该方法完成线程执行的一些初始化工作。假设

一个多线程继承了Thread实现,类名为MyThread,而另一个类实现Runnable接口实现多线程,类名为MyRunThread,则两者启动线程的方式如下:

//创建类MyThread的对象thread,并启动该线程
MyThread thread =new MyThread();
thread.start();
//创建类MyRunThread()的对象myRunThread,并启动该线程
Thread myRunThread = new Thread(new myRunThread());
myRunThread.start();

由于Runnable接口并没有任何对线程的支持,所以必须创建Thread类的实例,这是通过Thread类的一个构造函数public Thread(Runnable target)实现的。所以通过接口创建

线程对象启动时,需要借助Thread类的start()方法完成线程启动的初始化工作。

(2)线程的休眠

Java提供了一种控制线程的方法sleep(int miliseconds),这里称为线程的休眠,它将线程停止一段时间,该时间由方法的参数决定,当时间结束时线程进入就绪状态,可以

抢占CPU的时间周期

代码示例:

//类SynDefi通过继承Thread父类实现多线程
public class SynDefi extends Thread {
//定义参数count记录线程的执行,number作为线程计数器
int count=1,number;
//定义构造函数,参数为int型,标示新建的线程
public SynDefi(int num){
number=num;
System.out.println("创建线程"+number);
}
//run()方法内的代码是线程执行的内容,这些代码在执行过程中可能被打断执行
public void run(){
while(true){
System.out.println("线程"+number+":计数"+count);
if(++count==10) return;
//设置sleep()方法,执行完一次while循环,线程休眠100毫秒
try{
sleep(100);
}
catch(InterruptedException ex){
throw new RuntimeException(ex);
}
}
}
}

//定义程序主类
public class Test {  
public static void main(String[] args) { 
for(int i=0;i<3;i++)
new SynDefi(i+1).start();
}
}


注意:调用对象的sleep()方法时,一定要把该方法放在try块内,由于线程可能被中断,所以线程中的该方法在执行过程中(休眠期间)就有可能被中断。使用try区块就可以很好的捕获这些异常。

代码说明:sleep()方法不是控制线程执行顺序的方法,它的作用是使线程停止执行一段时间,而后线程还是抢占式的。唯一可以确定的是线程将停止执行100毫秒,而一旦等待时间超过100毫秒,线程处于就绪状态,此时就等待线程调度机制来恢复线程的执行。

(3)等待和通知

等待和通知实现了线程之间的协调机制,使得线程之间可以建立“和谐”的协作关系,java提供了线程对象的wait()、notify()或notifyAll()方法来实现这种协作,wait()方法使线程挂起一段时间,而notify()或notifyAll()方法使线程从wait()方法调用的状态中恢复到就绪状态。

wait()和sleep()方法相似,都是让线程暂时挂起,都可以接受一个时间参数来确定线程挂起时间。但是wait()方法有如下特殊之处:

1》线程一旦调用wait()方法,线程中同步方法的锁被释放,其他线程可以调用该线程中相应的同步方法

2》使用wait()方法的线程可以使用notify()或notifyAll()方法获得执行的权利,即抢占CPU周期的权利。

     wait()方法可以使线程在等待外部输入条件时,让线程暂时休眠,等待notify()或notifyAll()方法来唤醒线程以检查是否有外部条件的输入。可见wait()方法为线程之间的同步提供了方法。

 代码示例:

public class Cone {
  private String mydata;
  private boolean flag=false;
  public synchronized String getData(){
 while(flag==false){
 try{
 //调用wait()方法,释放对象锁,等待下一次获得对象锁
 wait();
 }catch(InterruptedException ex){}
 }
 //设置标志变量flag为false后,通知所有等待该对象锁的线程
 flag=false;
 notifyAll();
 return mydata;
  }
  
  public synchronized void setData(String str){
while(flag==true){
try{
//flag的值不满足方法继续执行条件,调用wait()方法释放对象锁,等待下一次获得对象锁
wait();
}catch(InterruptedException ex){}
 
}  
mydata=str;
//设置标志变量flag为true后,通知所有等待该对象锁的线程
flag=true;
notifyAll();
  }
}

在方法的适当位置增加了wait()和notifyAll()方法后,就满足了我们的要求。此时如果线程A可以释放锁并等待,另一个线程获得了对象锁,并调用了方法setData(),设置了标志

变量flag为true,在退出方法调用时调用notifyAll()方法,通知所有等待对象锁的线程,那么线程A将获得对象锁,满足运行条件继续执行。

在唤醒线程时推荐优先使用notifyAll(),而不是notity(),因为notify()仅唤醒一个线程。如果用户知道只有一个线程处于等待状态,这是可行的。但是当多个线程处于等待状态,用户就无法预期被唤醒的是哪个线程,而这由JVM决定,用户无法控制。

假设有两个线程处于等待状态,其中一个等待某个输入条件,后来完成输入条件的那段代码调用了notify(),但它只能换洗净一个线程。由于连个线程都在等待,所以等待输入条件的线程不见得被唤醒。因此使用notify()唤醒线程需要一定的条件:

(1)用户确定只有一个线程处于等待状态。

(2)多个线程处于等待状态且等待同样的条件,这样总有一个线程被唤醒,但是仍无法确定是哪个线程被唤醒。

 notifyAll唤醒所有等待状态的线程。只要有一段代码调用该函数就可以确保所有等待中的线程都将被唤醒。所以在使用唤醒机制时最好使用notifyAll,它唤醒所有等待中的线程,但是需要注意被唤醒的这些线程并不是都获得了对象锁,而是需要抢占对象锁。这种抢占顺序是无法预料的。

(4)结束线程

线程退出run()方法,线程死亡。

5)线程间通信

线程间进行输入/输出通信最常用的方式是“管道”方式。Java线程支持这种形式的通信,即一个线程从管道一段写入数据,另一个线程从管道对端读出数据。在java的输入输出

类库中两个类PipedWriter和PipedReader都支持管道通信方式。前者允许向管道写数据,后者允许不同的线程从同一个管道读数据。

(1)PipedWriter

PipedWriter类的作用是创建一个PipedWriter类对象writer,链接到一个PipedReader类对象reader,从writer写入的数据从reader可以读出。

该类的声明方式有两种:

1》public PipedWriter(PipedReader reader) throw IOException{/*类主体*/}

  类的构造函数参数为PipedReader类对象,明确了建立管道链接的两个对象。

2》public PipedWriter() throw IOException{/*类主体*/}

类的构造函数没有参数,在建立管道链接时必须说明链接的对象(PipedReader类对象)。该类的方法详解:

public void connect (PipedReader reader) throw IOException

该方法使PipedWriter对象链接PipedReader对象。如果该PipedWriter对象已经链接到PipedReader对象,则抛出IOException异常。如果Reader是未链接对象,而且PipedWriter对象writer也是未链接对象,两者可以使用如下方式建立链接且效果相同。writer.connect(reader) 或者reader.connect(writer)

public int write() throws IOException:该方法的作用是向管道中写入字符。

public int write(char[] cbuf,int off,int length) throws IOException:该字符的作用是从数组中读取字符,从数组cbuf中第off个字符开始,长度为length.

public boolean flush() throws IOException :该方法是把输出流中的数据全部输入,并且强迫缓冲区中的所有数据全部写入输出流。

public void close() throws IOException:该方法关闭管道流,并且释放和管道流相关的链接资源。如果有输入输出错误,则抛出IOException

(2) PipedReader

PipedReader类的作用是创建一个PipeReader类对象reader,链接到一个PipedWriter类对象writer,从writer写入的数据在reader中可以轻松读出,该类的声明方式有两种:

1》public PipedReader(PipedWriter writer) throw IOException{/*类主体*/}

类的构造方法参数为PipedWriter类对象,明确了建立管道链接的两个对象

2》public PipedReader() throw IOException{/*类主体*/}

类的构造方法没有参数,在建立管道链接的时候必须说明链接的对象(PipedWriter对象)

该类的方法详解:

public void connect(PipedWriter writer) throws IOException;该方法使PipedReader对象链接PipedWriter对象。如果该PipedReader对象已经链接到其他PipedWriter对象,则

抛出IOException异常。如果writer对象是未链接的对象并且Pipereader对象reader也是未链接的对象,二者可以使用如下方式建立链接,且效果相同:

reader.connect(writer) 或者writer.connect(reader)

public int read() throws IOException:该方法的作用是读取来自管道的字符流的下一个字符。如果读完管道字符流而无法再获得数据,则返回值为-1.该方法在可以获得数据前或异常发生时会一直阻塞

public int read(char[] cbuf,int off,int length) throws IOException:该方法的作用是 读取字符流中从第off个字符开始的length个字符,并存储到字符数组cbuf中。如果字符流中的

字符总数少于10个,则该方法读取字符流中所有字符。

public Boolean ready() throws IOException:该方法的作用是判断是否准备好读取已经建立的管道字符流中的字符,如果循环缓冲区不空则说明该流已经准备链接。

public void close() throws IOException:该方法关闭管道流,并释放和管道流相关的链接资源。如果有输入输出错误,则抛出IOException异常。

管道通信实例:



import java.io.PipedWriter;
import java.util.Random;


public class PipeSender extends Thread {
  private Random random = new Random();
  //创建PipedWriter类对象,通过该对象向管道中写字符数据
  private PipedWriter out = new PipedWriter();
  public PipedWriter getPipedWriter(){
 return out;
 }
  public void run(){
 //通过一个无限循环,向管道写入字符,字符序列从A到Z,且循环输出
 while(true){
 for(char c='A';c<='Z';c++){
 try{
//通过对象out向管道写字符数据,每写入一个字符停止500毫秒
 out.write(c);
 sleep(random.nextInt(500));
 }catch(Exception ex){
 throw new RuntimeException(ex);
 }
 }
 }
  }
}


import java.io.IOException;
import java.io.PipedReader;


//创建类PipedReceiver,该类的对象读取管道中的字符数据
public class PipeReceiver extends Thread {
 private PipedReader in;
 //类PipedReceiver的构造函数,通过类PipedReader的带参数的构造函数创建一个PipedReader对象in
 public PipeReceiver(PipeSender sender) throws IOException{

in = new PipedReader(sender.getPipedWriter());
 }
 //通过对象in读取管道中的字符,并把int型数据转换成字符打印到屏幕上
  public void run(){
 try{
 while(true){
 System.out.println("Read_data is:"+(char) in.read());
 }
 }catch(IOException ex){
 throw new RuntimeException(ex);
 }
 }
  }


import java.io.IOException;


//定义程序主类
public class Test {  
public static void main(String[] args) throws IOException { 
 //创建线程对象sender
PipeSender sender = new PipeSender();
//创建线程对象receiver,该对象类的构造函数参数为对象sender
PipeReceiver receiver =new PipeReceiver(sender);
sender.start();
receiver.start();
}
}


6)多线程的缺点

多线程的主要目的是对大量并行的任务进行有序的管理,通过同时执行多个任务,可以有效地利用计算机资源(主要是提高CPU的利用率),或者实现对用户来讲响应及时的程序界面。使用多线程的缺点:

(1)等待访问共享资源时,多线程使程序运行变慢。如果用户访问网络数据库,而恰好数据库的访问是互斥的,所以一个线程在访问大量数据或修改大量数据时,其他线程就只能等待而不能执行。

(2)当线程数量增多时,对多线程的管理需要额外的CPU开销。

 (3)死锁是难以避免的,只有依靠程序员谨慎的设计多线程程序来尽量避免

(4)随意使用多线程技术有时会耗费系统资源,所以要求程序员知道何时使用多线程以及何时放弃使用该技术


7)进程和线程的区别

进程和线程的主要差别在于它们是不同的操作系统管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但是线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多进程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值