服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 高级技术 > 多线程 > 查看文档

解析java的多线程机制


  进程与应用程序的区别
  进程(process)是最初定义在unix等多用户、多任务操作系统环境下用于表示应用程序在内存环境中基本执行单元的概念。以unix操作系统为例,进程是unix操作系统环境中的基本成分、是系统资源分配的基本单位。unix操作系统中完成的几乎所有用户管理和资源分配等工作都是通过操作系统对应用程序进程的控制来实现的。
  c、c++、java等语言编写的源程序经相应的编译器编译成可执行文件后,提交给计算机处理器运行。这时,处在可执行状态中的应用程序称为进程。从用户角度来看,进程是应用程序的一个执行过程。从操作系统核心角度来看,进程代表的是操作系统分配的内存、cpu时间片等资源的基本单位,是为正在运行的程序提供的运行环境。进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的系统资源管理实体。多任务环境下应用程序进程的主要特点包括:
  ●进程在执行过程中有内存单元的初始入口点,并且进程存活过程中始终拥有独立的内存地址空间;
  ●进程的生存期状态包括创建、就绪、运行、阻塞和死亡等类型;
  ●从应用程序进程在执行过程中向cpu发出的运行指令形式不同,可以将进程的状态分为用户态和核心态。处于用户态下的进程执行的是应用程序指令、处于核心态下的应用程序进程执行的是操作系统指令。
  在unix操作系统启动过程中,系统自动创建swapper、init等系统进程,用于管理内存资源以及对用户进程进行调度等。在unix环境下无论是由操作系统创建的进程还要由应用程序执行创建的进程,均拥有唯一的进程标识(pid)。
  进程与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中定义的用于控制线程对象的类,在该类中封装了用于进行线程控制的方法。见下面的示例代码:
  //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();
  }
  }
  从上面的程序代码可以看出:多线程执行地下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)

扫描关注微信公众号