`
z274084093
  • 浏览: 17354 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

进程与Java线程的区别

阅读更多

应用程序在执行过程中存在一个内存空间的初始入口点地址、一个程序执行过程中的代码执行序列以及用于标识进程结束的内存出口点地址,在进程执行过程中的每一时间点均有唯一的处理器指令与内存单元地址相对应。

Java语言中定义的线程(Thread)同样包括一个内存入口点地址、一个出口点地址以及能够顺序执行的代码序列。但是进程与线程的重要区别在于线程不能够单独执行,它必须运行在处于活动状态的应用程序进程中,因此可以定义线程是程序内部的具有并发性的顺序代码流。

Unix操作系统和Microsoft Windows操作系统支持多用户、多进程的并发执行,而Java语言支持应用程序进程内部的多个执行线程的并发执行。多线程的意义在于一个应用程序的多 个逻辑单元可以并发地执行。但是多线程并不意味着多个用户进程在执行,操作系统也不把每个线程作为独立的进程来分配独立的系统资源。进程可以创建其子进 程,子进程与父进程拥有不同的可执行代码和数据内存空间。而在用于代表应用程序的进程中多个线程共享数据内存空间,但保持每个线程拥有独立的执行堆栈和程 序执行上下文(Context)。

基于上述区别,线程也可以称为轻型进程 (Light Weight Process,LWP)。不同线程间允许任务协作和数据交换,使得在计算机系统资源消耗等方面非常廉价。

线程需要操作系统的支持,不是所有类型的计算机都支持多线程应用程序。Java程序设计语言将线程支持与语言运行环境结合在一起,提供了多任务并发执行的 能力。这就好比一个人在处理家务的过程中,将衣服放到洗衣机中自动洗涤后将大米放在电饭锅里,然后开始做菜。等菜做好了,饭熟了同时衣服也洗好了。

需要注意的是:在应用程序中使用多线程不会增加 CPU 的数据处理能力。只有在多CPU 的计算机或者在网络计算体系结构下,将Java程序划分为多个并发执行线程后,同时启动多个线程运行,使不同的线程运行在基于不同处理器的Java虚拟机 中,才能提高应用程序的执行效率。

另外,如果应用程序必须等待网络连接或数据库连接等数据吞吐速度相对较慢的资源时,多线程应用程序是非常有利的。基于Internet的应用程序有必要是 多线程类型的,例如,当开发要支持大量客户机的服务器端应用程序时,可以将应用程序创建成多线程形式来响应客户端的连接请求,使每个连接用户独占一个客户 端连接线程。这样,用户感觉服务器只为连接用户自己服务,从而缩短了服务器的客户端响应时间。


三、Java语言的多线程程序设计方法


利用Java语言实现多线程应用程序的方法很简单。根据多线程应用程序继承或实现对象的不同可以采用两种方式:一种是应用程序的并发运行对象直接继承Java的线程类Thread;另外一种方式是定义并发执行对象实现Runnable接口。

继承Thread类的多线程程序设计方法

Thread 类是JDK中定义的用于控制线程对象的类,在该类中封装了用于进行线程控制的方法。见下面的示例代码:

[code]//Consumer.java
import java.util.*;
class Consumer extends Thread
{
int nTime;
String strConsumer;
public Consumer(int nTime, String strConsumer)
{
this.nTime = nTime;
this.strConsumer = strConsumer;
}
public void run()
{
while(true)
{
try
{
System.out.println("Consumer name:"+strConsumer+"\n");
Thread.sleep(nTime);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
static public void main(String args[])
{
Consumer aConsumer = new Consumer (1000, "aConsumer");
aConsumer.start();
Consumer bConsumer = new Consumer (2000, "bConsumer");
bConsumer.start();
Consumer cConsumer = new Consumer (3000, "cConsumer ");
cConsumer.start();
}
} [/code]




从上面的程序代码可以看出:多线程执行地下Consumer继承Java语言中的线程类Thread并且在main方法中创建了三个Consumer对象 的实例。当调用对象实例的start方法时,自动调用Consumer类中定义的run方法启动对象线程运行。线程运行的结果是每间隔nTime时间打印 出对象实例中的字符串成员变量strConsumer的内容。

可以总结出继承Thread类的多线程程序设计方法是使应用程序类继承Thread类并且在该类的run方法中实现并发性处理过程。

实现Runnable接口的多线程程序设计方法

Java语言中提供的另外一种实现多线程应用程序的方法是多线程对象实现Runnable接口并且在该类中定义用于启动线程的run方法。这种定义方式的好处在于多线程应用对象可以继承其它对象而不是必须继承Thread类,从而能够增加类定义的逻辑性。

实现Runnable接口的多线程应用程序框架代码如下所示:

//Consumer.java
import java.util.*;
class Consumer implements Runnable
{
… …
public Consumer(int nTime, String strConsumer){… …}
public void run(){… …}
static public void main(String args[])
{
Thread aConsumer = new Thread(new Consumer(1000, "aConsumer"));
aConsumer.start();
//其它对象实例的运行线程
//… …
}
}

从上述代码可以看出:该类实现了Runnable接口并且在该类中定义了run方法。这种多线程应用程序的实现方式与继承Thread类的多线程应用程序 的重要区别在于启动多线程对象的方法设计方法不同。在上述代码中,通过创建Thread对象实例并且将应用对象作为创建Thread类实例的参数。

四、线程间的同步

Java应用程序的多个线程共享同一进程的数据资源,多个用户线程在并发运行过程中可能同时访问具有敏感性的内容。在Java中定义了线程同步的概念,实 现对共享资源的一致性维护。下面以笔者最近开发的移动通信计费系统中线程间同步控制方法,说明Java语言中多线程同步方式的实现过程。

在没有多线程同步控制策略条件下的客户账户类定义框架代码如下所示:

public class RegisterAccount
{
float fBalance;
//客户缴费方法
public void deposit(float fFees){ fBalance += fFees; }
//通话计费方法
public void withdraw(float fFees){ fBalance -= fFees; }
… …
}





读者也许会认为:上述程序代码完全能够满足计费系统实际的需要。确实,在单线程环境下该程序确实是可靠的。但是,多进程并发运行的情况是怎样的呢?假设发 生这种情况:客户在客户服务中心进行缴费的同时正在利用移动通信设备仅此通话,客户通话结束时计费系统启动计费进程,而同时服务中心的工作人员也提交缴费 进程运行。读者可以看到如果发生这种情况,对客户账户的处理是不严肃的。

如何解决这种问题呢?很简单,在RegisterAccount类方法定义中加上用于标识同步方法的关键字synchronized。这样,在同步方法执 行过程中该方法涉及的共享资源(在上述代码中为fBalance成员变量)将被加上共享锁,以确保在方法运行期间只有该方法能够对共享资源进行访问,直到 该方法的线程运行结束打开共享锁,其它线程才能够访问这些共享资源。在共享锁没有打开的时候其它访问共享资源的线程处于阻塞状态。

进行线程同步策略控制后的RegisterAccount类定义如下面代码所示:

public class RegisterAccount
{
float fBalance;
public synchronized void deposit(float fFees){ fBalance += fFees; }
public synchronized void withdraw(float fFees){ fBalance -= fFees; }
… …
}


从经过线程同步机制定义后的代码形式可以看出:在对共享资源进行访问的方法访问属性关键字(public)后附加同步定义关键字 synchronized,使得同步方法在对共享资源访问的时候,为这些敏感资源附加共享锁来控制方法执行期间的资源独占性,实现了应用系统数据资源的一 致性管理和维护。


五、 Java线程的管理


线程的状态控制

在这里需要明确的是:无论采用继承Thread类还是实现Runnable接口来实现应用程序的多线程能力,都需要在该类中定义用于完成实际功能的run 方法,这个run方法称为线程体(Thread Body)。按照线程体在计算机系统内存中的状态不同,可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为:

创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源;

就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。

睡眠状态:在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。

挂起状态:可以通过调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法恢复线程运行。

死亡状态:当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。

在Java线程类中分别定义了相应的方法,用于在应用程序中对线程状态进行控制和管理。

线程的调度

线程调用的意义在于JVM应对运行的多个线程进行系统级的协调,以避免多个线程争用有限资源而导致应用系统死机或者崩溃。

为了线程对于操作系统和用户的重要性区分开,Java定义了线程的优先级策略。Java将线程的优先级分为10个等级,分别用1-10之间的数字表示。数 字越大表明线程的级别越高。相应地,在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、 MAX_PRIORITY和NORMAL_PRIORITY,代表的优先级等级分别为1、10和5。当一个线程对象被创建时,其默认的线程优先级是5。

为了控制线程的运行策略,Java定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器按照线程的优先级决定那个线程投入处理器运行。在多个 线程处于就绪状态的条件下,具有高优先级的线程会在低优先级线程之前得到执行。线程调度器同样采用"抢占式"策略来调度线程执行,即当前线程执行过程中有 较高优先级的线程进入就绪状态,则高优先级的线程立即被调度执行。具有相同优先级的所有线程采用轮转的方式来共同分配CPU时间片。

在应用程序中设置线程优先级的方法很简单,在创建线程对象之后可以调用线程对象的setPriority方法改变该线程的运行优先级,同样可以调用getPriority方法获取当前线程的优先级。

在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。将一个用 户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在 低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

线程分组管理

Java定义了在多线程运行系统中的线程组(ThreadGroup)对象,用于实现按照特定功能对线程进行集中式分组管理。用户创建的每个线程均属于某 线程组,这个线程组可以在线程创建时指定,也可以不指定线程组以使该线程处于默认的线程组之中。但是,一旦线程加入某线程组,该线程就一直存在于该线程组 中直至线程死亡,不能在中途改变线程所属的线程组。

当Java的Application应用程序运行时,JVM创建名称为main的线程组。除非单独指定,在该应用程序中创建的线程均属于main线程组。 在main线程组中可以创建其它名称的线程组并将其它线程加入到该线程组中,依此类推,构成线程和线程组之间的树型管理和继承关系。

与线程类似,可以针对线程组对象进行线程组的调度、状态管理以及优先级设置等。在对线程组进行管理过程中,加入到某线程组中的所有线程均被看作统一的对象。

六、小结:
本文针对Java平台中线程的性质和应用程序的多线程策略进行了分析和讲解。

与其它操作系统环境不同,Java运行环境中的线程类似于多用户、多任务操作系统环境下的进程,但在进程和线程的运行及创建方式等方面,进程与Java线程具有明显区别。

Unix操作系统环境下,应用程序可以利用fork函数创建子进程,但子进程与该应用程序进程拥有独立的地址空间、系统资源和代码执行单元,并且进程的调 度是由操作系统来完成的,使得在应用进程之间进行通信和线程协调相对复杂。而Java应用程序中的多线程则是共享同一应用系统资源的多个并行代码执行体, 线程之间的通信和协调方法相对简单。

可以说:Java语言对应用程序多线程能力的支持增强了Java作为网络程序设计语言的优势,为实现分布式应用系统中多客户端的并发访问以及提高服务器的响应效率奠定坚实基础。

Java的线程编程非常简单。但有时会看到一些关于线程的错误用法。下面列出一些应该注意的问题。

1.同步对象的恒定性
All java objects are references.

对于局部变量和参数来说,java里面的int, float, double, boolean等基本数据类型,都在栈上。这些基本类型是无法同步的;java里面的对象(根对象是Object),全都在堆里,指向对象的reference在栈上。

java中的同步对象,实际上是对于reference所指的“对象地址”进行同步。
需要注意的问题是,千万不要对同步对象重新赋值。举个例子。
class A implements Runnable{
Object lock = new Object();

void run(){
for(...){
synchronized(lock){
// do something
...
lock = new Object();
}
}
}

run函数里面的这段同步代码实际上是毫无意义的。因为每一次lock都给重新分配了新的对象的reference,每个线程都在新的reference同步。
大家可能觉得奇怪,怎么会举这么一个例子。因为我见过这样的代码,同步对象在其它的函数里被重新赋了新值。
这种问题很难查出来。
所以,一般应该把同步对象声明为final.
final Object lock = new Object();



使用Singleton Pattern 设计模式来获取同步对象,也是一种很好的选择。

2.如何放置共享数据
实现线程,有两种方法,一种是继承Thread类,一种是实现Runnable接口。

上面举的例子,采用实现Runnable接口的方法。本文推荐这种方法。

首先,把需要共享的数据放在一个实现Runnable接口的类里面,然后,把这个类的实例传给多个Thread的构造方法。这样,新创建的多个Thread,都共同拥有一个Runnable实例,共享同一份数据。

如果采用继承Thread类的方法,就只好使用static静态成员了。如果共享的数据比较多,就需要大量的static静态成员,令程序数据结构混乱,难以扩展。这种情况应该尽量避免。

编写一段多线程代码,处理一个稍微复杂点的问题。两种方法的优劣,一试便知。

3.同步的粒度
线程同步的粒度越小越好,即,线程同步的代码块越小越好。尽量避免用synchronized修饰符来声明方法。尽量使用 synchronized(anObject)的方式,如果不想引入新的同步对象,使用synchronized(this)的方式。而 且,synchronized代码块越小越好。

4.线程之间的通知
这里使用“通知”这个词,而不用“通信”这个词,是为了避免词义的扩大化。

线程之间的通知,通过Object对象的wait()和notify() 或notifyAll() 方法实现。

下面用一个例子,来说明其工作原理:

假设有两个线程,A和B。共同拥有一个同步对象,lock。

1.首先,线程A通过synchronized(lock) 获得lock同步对象,然后调用lock.wait()函数,放弃lock同步对象,线程A停止运行,进入等待队列。

2.线程B通过synchronized(lock) 获得线程A放弃的lock同步对象,做完一定的处理,然后调用 lock.notify() 或者lock.notifyAll() 通知等待队列里面的线程A。

3.线程A从等待队列里面出来,进入ready队列,等待调度。

4.线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。

5.线程A获得lock同步对象,继续运行。



例子代码如下:

public class SharedResource implements Runnable{

Object lock = new Object();



public void run(){

// 获取当前线程的名称。

String threadName = Thread.currentThread().getName();



if( “A”.equals(threadName)){

synchronized(lock){ //线程A通过synchronized(lock) 获得lock同步对象

try{

System.out.println(“ A gives up lock.”);

lock.wait(); // 调用lock.wait()函数,放弃lock同步对象,

// 线程A停止运行,进入等待队列。

}catch(InterruptedException e){

}



// 线程A重新获得lock同步对象之后,继续运行。

System.out.println(“ A got lock again and continue to run.”);

} // end of synchronized(lock)

}



if( “B”.equals(threadName)){

synchronized(lock){//线程B通过synchronized(lock) 获得线程A放弃的lock同步对象

System.out.println(“B got lock.”);



lock.notify(); //通知等待队列里面的线程A,进入ready队列,等待调度。



//线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。

System.out.println(“B gives up lock.”);

} // end of synchronized(lock)



boolean hasLock = Thread.holdsLock(lock); // 检查B是否拥有lock同步对象。

System.out.println(“B has lock ? -- ” +hasLock); // false.

}

}

}



public class TestMain{

public static void main(){

Runnable resource = new SharedResource();



Thread A = new Thread(resource,”A”);

A.start();



// 强迫主线程停止运行,以便线程A开始运行。

try {

Thread.sleep(500);

}catch(InterruptedException e){

}



Thread B = new Thread(resource,”B”);

B.start();

}

}



5.跨类的同步对象
对于简单的问题,可以把访问共享资源的同步代码都放在一个类里面。

但是对于复杂的问题,我们需要把问题分为几个部分来处理,需要几个不同的类来处理问题。这时,就需要在不同的类中,共享同步对象。比如,在生产者和消费者之间共享同步对象,在读者和写者之间共享同步对象。

如何在不同的类中,共享同步对象。有几种方法实现,

(1)前面讲过的方法,使用static静态成员,(或者使用Singleton Pattern.)

(2)用参数传递的方法,把同步对象传递给不同的类。

(3)利用字符串常量的“原子性”。



对于第三种方法,这里做一下解释。一般来说,程序代码中的字符串常量经过编译之后,都具有唯一性,即,内存中不会存在两份相同的字符串常量。

(通常情况下,C++,C语言程序编译之后,也具有同样的特性。)

比如,我们有如下代码。

String A = “atom”;

String B = “atom”;

我们有理由认为,A和B指向同一个字符串常量。即,A==B。

注意,声明字符串变量的代码,不符合上面的规则。

String C= new String(“atom”);

String D = new String(“atom”);

这里的C和D的声明是字符串变量的声明,所以,C != D。



有了上述的认识,我们就可以使用字符串常量作为同步对象。

比如我们在不同的类中,使用synchronized(“myLock”), “myLock”.wait(),“myLock”.notify(), 这样的代码,就能够实现不同类之间的线程同步。

本文并不强烈推荐这种用法,只是说明,有这样一种方法存在。



本文推荐第二种方法,(2)用参数传递的方法,把同步对象传递给不同的类。

分享到:
评论

相关推荐

    JAVA线程与进程的区别

    "JAVA线程与进程的区别" JAVA语言中,线程(Thread)和进程(Process)是两个基本概念,它们都是操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。但是,它们之间有着本质的区别。 ...

    Java线程讲解Java线程讲解

    1. **线程与进程的区别**:线程是进程中可独立调度和执行的基本单元,而进程则是资源分配的基本单位。一个进程中可以包含多个线程。 2. **线程的好处**:通过多线程技术,可以显著提高程序的响应速度和资源利用率...

    Java线程.ppt

    线程与进程的主要区别在于,进程拥有独立的内存空间和系统资源,而线程则是轻量级的进程,它们共享同一块内存空间和系统资源,因此线程间的切换成本较低。多线程可以提高程序的并发性,尤其在有多处理器的系统中,每...

    进程和线程的区别与联系

    进程和线程是计算机操作系统中的两个基本概念,它们在程序执行和系统资源管理中起着至关重要的作用。本文将深入探讨这两个概念的区别、联系以及在Java环境中的具体应用。 首先,程序是静态的代码集合,它是一组指令...

    输出java进程的jstack信息示例分享 通过线程堆栈信息分析java线程

    在分析Java应用程序的性能问题时,了解和分析Java线程的状态是非常关键的。线程堆栈信息可以让我们深入观察到线程的运行状况,包括线程的当前状态、调用堆栈、锁信息等。为了获取这些信息,我们可以使用jstack工具,...

    java实现守护进程,有单独的监听进程, 两个或多个进程,两个或多个jvm

    Java中的`java.lang.Runtime`和`java.lang.ProcessBuilder`类可以帮助我们启动新的进程,而`java.util.concurrent`包下的线程类则可以用来创建守护线程。 首先,让我们了解如何创建一个监听进程。监听进程通常是...

    java线程分析工具TDA

    Java线程分析是Java开发中的重要环节,尤其是在处理性能优化、死锁排查或者并发问题时。TDA(Thread Dump Analyzer)是一款强大的Java线程分析工具,它能够帮助开发者深入理解应用在运行时的线程状态,包括线程的...

    java线程使用教程

    - **与进程的区别**:与进程相比,线程之间的隔离度更低,它们共享相同的内存空间和其他进程资源。这种特性使得线程间的通信更为高效,但也需要特别注意同步和互斥问题,以免出现数据不一致或死锁等问题。 - **Java...

    Java 的多线程,程序、进程和线程的概念31

    2. **线程的启动与生命周期**:通过调用Thread对象的start()方法启动线程,线程会经历新建(New)、就绪(Runnable)、运行(Running)、等待/阻塞(Blocked/Wait)和终止(Terminated)五个状态。 3. **线程同步**...

    java线程深入解析

    Java线程是Java编程语言中的核心概念,尤其在多任务处理和并发编程中扮演着重要角色。线程允许一个程序内部同时执行多个独立的控制流,使得程序能够更高效地利用处理器资源。本文将深入解析Java线程的相关知识点,...

    Java的线程和Java AppletJava的线程和Java AppletJava的线程和Java Applet

    1. **线程与进程**:线程是进程内的一个执行单元,多个线程共享进程的内存空间和资源,而进程是操作系统分配资源的基本单位,每个进程拥有独立的内存区域。 2. **线程模型**:Java中的线程由虚拟CPU(封装在`java....

    小java线程监控程序

    有点乐趣,小java线程监控程序, 小java线程监控程序 小java线程监控程序

    java线程进程锁资源

    **进程与线程的区别** 1. **进程**:是操作系统资源分配的基本单位,每个进程都有独立的内存空间,包括程序代码、数据和栈空间等。 2. **线程**:是CPU调度的基本单位,同一进程内的线程共享进程的内存空间和资源,...

    JAVA线程dump的分析

    生成JAVA线程dump的方法在不同的操作系统下是不同的,在Windows环境中,可以敲击Ctrl-Break键,在Unix、Linux和MacOS环境中,可以敲击Ctrl-\键或使用“kill -3 ”命令,Pid是关注的JAVA进程号。 在分析JAVA线程dump...

    Java线程使用教程

    Java线程是Java编程语言中的一个核心概念,它允许程序同时执行多个任务,极大地提高了程序的并发性和效率。本教程将深入探讨Java线程的使用,帮助开发者掌握这一关键技术。 一、线程基础 1. **线程的概念**:线程...

    java 多线程设计模式 进程详解

    《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口的线程 线程的生命周期 线程命名 ...

    Java 多线程 PPT

    创建Java线程主要有两种方式: 1. 继承Thread类:创建Thread的子类并重写run()方法,然后通过调用start()方法启动线程。 2. 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,然后将该类的实例...

    Java线程 学习

    - **进程与线程的区别**:进程是在某种程度上相互隔离的独立运行的程序。线程则是进程内部的并发执行单元。同一个进程中的线程共享相同的内存空间。 - **线程的优势**:使用线程可以提高用户界面的响应速度、充分...

Global site tag (gtag.js) - Google Analytics