电话:0731-83595998
导航

Java语言深入--对JAVA的多线程浅析

来源: 2017-12-21 10:59

  一 JAVA 语言的、及特点

  在这个高速信息的时代,商家们纷纷把信息、产品做到Internet国际互连网页上。再这些不寻常网页的背后,要属功能齐全、安全可靠的编程语言,Java是当之无愧的。Java是由Sun Microsystem开发的一种功能强大的新型程序设计语言。是与平台无关的编程语言。它是一种简单的、面象对象的、分布式的、解释的、键壮的、安全的、结构的中立的、可移植的、性能很优异的、多线程的、动态的、语言。

  Java自问世以后,以其编程简单、代码高效、可移植性强,很快受到了广大计算机编程人士的青睐。Java语言是Internet上具有革命性的编程语言,它具有强大的动画、多媒体和交互功能,他使World Web进入了一个全新的时代。Java语言与C++极为类似,可用它来创建安全的、可移植的、多线程的交互式程序。另外用Java开发出来的程序与平台无关,可在多种平台上运行。后台开发,是一种高效、实用的编程方法。人们在屏幕前只能看到例如图案、计算的结果等。实际上操作系统往往在后台来调度一些事件、管理程序的流向等。例如操作系统中的堆栈,线程间的资源分配与管理,内存的创建、访问、管理等。可谓举不盛举。下面就多线程来谈一谈。

  二 JAVA的多线程理论

  2.1引入

  Java提供的多线程功能使得在一个程序里可同时执行多个小任务。线程有时也称小进程是一个大进程里分出来的小的独立的进程。因为Java实现的多线程技术,所以比C和C++更键壮。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX,Windows,Macintosh等),在开发难易程度和性能上都比单线程要好。传统编程环境通常是单线程的,由于JAVA是多线程的。尽管多线程是强大而灵巧的编程工具,但要用好却不容易,且有许多陷阱,即使编程老手也难免误用。为了更好的了解线程,用办公室工作人员作比喻。办公室工作人员就象CPU,根据上级指示做工作,就象执行一个线程。在单线程环境中,每个程序编写和执行的方式是任何时候程序只考虑一个处理顺序。用我们的比喻,就象办公室工作人员从头到尾不受打扰和分心,只安排做一个工作。当然,实际生活中工作人员很难一次只有一个任务,更常见的是工作人员要同时做几件事。老板将工作交给工作人员,希望工作人员做一这个工作,再做点那个工作,等等。如果一个任务无法做下去了,比如工作人员等待另一部门的信息,则工作人员将这个工作放在一边,转入另一个工作。一般来说,老板希望工作人员手头的各个任务每一天都有一些进展。这样就引入了多线程的概念。多线程编程环境与这个典型的办公室非常相似,同时给CPU分配了几个任务或线程。和办公室人员一样,计算机CPU实际上不可能同一时间做几件事,而是把时间分配到不同的线程,使每个线程都有点进展。如果一个线程无法进行,比如线程要求的键盘输入尚未取得,则转入另一线程的工作。通常,CPU在线程间的切换非常迅速,使人们感觉到好象所有线程是同时进行的。

  任何处理环境,无论是单线程还是多线程,都有三个关键方面。第一个是CPU,它实际上进行计算机活动;第二个是执行的程序的代码;第三个是程序操作的数据。

  在多线程编程中,每个线程都用编码提供线程的行为,用数据供给编码操作。多个线程可以同时处理同一编码和数据,不同的线程也可能各有不同的编码和数据。事实上编码和数据部分是相当独立的,需要时即可向线程提供。因此经常是几个线程使用同一编码和不同的数据。这个思想也可以用办公室工作人员来比喻。会计可能要做一个部门的帐或几个或几个部门的帐。任何情况的做帐的任务是相同的程序代码,但每个部门的数据是不同的。会计可能要做整个公司的帐,这时有几个任务,但有些数据是共享的,因为公司帐需要来自各个部门的数据。

  多线程编程环境用方便的模型隐藏CPU在任务切换间的事实。模型允许假装成有多个可用的CPU。为了建立另一个任务,编程人员要求另一个虚拟CPU,指示它开始用某个数据组执行某个程序段。下面我们来建立线程。

  建立线程

  在JAVA中建立线程并不困难,所需要的三件事:执行的代码、代码所操作的数据和执行代码的虚拟CPU。虚拟CPU包装在Thread类的实例中。建立Thread对象时,必须提供执行的代码和代码所处理的数据。JAVA的面向对象模型要求程序代码只能写成类的成员方法。数据只能作为方法中的自动(或本地)变量或类的成员存在。这些规则要求为线程提供的代码和数据应以类的实例的形式出现。

  Public class SimpleRunnable implemants Runable{

  Private String message;

  Public static void main(String args){

  SimpleRunnable r1=new SimpleRunnable("Hello");

  Thread t1=new Thread(r1);

  t1.start();

  }

  public SimpleRunnable(String message){

  this.message=message;

  }

  public void run(){

  for(;;){

  System.out.println(message);

  }

  }

  }

  线程开始执行时,它在public void run()方法中执行。这种方法是定义的线程执行的起点,就象应用程序从main()开始、小程序从init()开始一样。线程操作的本地数据是传入线程的对象的成员。

  首先,main()方法构造SimpleRunnable类的实例。注意,实例有自己的数据,这里是一个String,初始化为"Hello".由于实例r1传入Thread类构造器,这是线程运行时处理的数据。执行的代码是实例方法run()。

  2.2 线程的管理

  单线程的程序都有一个main执行体,它运行一些代码,当程序结束执行后,它正好退出,程序同时结束运行。在JAVA中我们要得到相同的应答,必须稍微进行改动。只有当所有的线程退出后,程序才能结束。只要有一个线程一直在运行,程序就无法退出。线程包括四个状态:new(开始),running(运行),wait(等候)和done(结束)。第一次创建线程时,都位于new状态,在这个状态下,不能运行线程,只能等待。然后,线程或者由方法start开始或者送往done状态,位于done中的线程已经结束执行,这是线程的最后一个状态。一旦线程位于这个状态,就不能再次出现,而且当JAVA虚拟机中的所有线程都位于done状态时,程序就强行中止。当前正在执行的所有线程都位于running状态,在程序之间用某种方法把处理器的执行时间分成时间片,位于running状态的每个线程都是能运行的,但在一个给定的时间内,每个系统处理器只能运行一个线程。与位于running状态的线程不同,由于某种原因,可以把已经位于waiting状态的线程从一组可执行线程中删除。如果线程的执行被中断,就回到waiting状态。用多种方法能中断一个线程。线程能被挂起,在系统资源上等候,或者被告知进入休眠状态。该状态的线程可以返回到running状态,也能由方法stop送入done状态.

  2.3线程的调度

  线程运行的顺序以及从处理器中获得的时间数量主要取决于开发者,处理器给每个线程分配一个时间片,而且线程的运行不能影响整个系统。处理器线程的系统或者是抢占式的,或者是非抢占式的。抢占式系统在任何给定的时间内将运行最高优先级的线程,系统中的所有线程都有自己的优先级。Thread.NORM_PRIORITY是线程的缺省值,Thread类提供了setPriority和getPriority方法来设置和读取优先权,使用setPriority方法能改变Java虚拟机中的线程的重要性,它调用一个整数,类变量Thread.MIN_PRIORITY和Thread.MAX_PRIORITY决定这个整数的有效范围。Java虚拟机是抢占式的,它能保证运行优先级最高的线程。在JAVA虚拟机中我们把一个线程的优先级改为最高,那么他将取代当前正在运行的线程,除非这个线程结束运行或者被一条休眠命令放入waiting状态,否者将一直占用所有的处理器的时间。如果遇到两个优先级相同的线程,操作系统可能影响线程的执行顺序。而且这个区别取决于时间片(time slicing)的概念。

  管理几个线程并不是真正的难题,对于上百个线程它是怎样管理的呢?当然可以通过循环,来执行每一个线程,但是这显然是冗长、乏味。JAVA创建了线程组。线程组是线程的一个谱系组,每个组包含的线程数不受限制,能对每个线程命名并能在整个线程组中执行(Suspend)和停止(Stop)这样的操作。 |||   2.4信号标志:保护其它共享资源

  这种类型的保护被称为互斥锁。某个时间只能有一个线程读取或修改这个数据值。在对文件尤其是信息数据库进行处理时,读取的数据总是多于写数据,根据这个情况,可以简化程序。下面举一例,假设有一个雇员信息的数据库,其中包括雇员的地址和电话号码等信息,有时要进行修改,但要更多的还是读数据,因此要尽可能防止数据被破坏或任意删改。我们引入前面互斥锁的概念,允许一个读取锁(red lock)和写入锁(write lock),可根据需要确定有权读取数据的人员,而且当某人要写数据时,必须有互斥锁,这就是信号标志的概念。信号标志有两种状态,首先是empty()状态,表示没有任何线程正在读或写,可以接受读和写的请求,并且立即提供服务;第二种状态是reading()状态,表示有线程正在从数据库中读信息,并记录进行读操作的线程数,当它为0时,返回empty状态,一个写请求将导致这个线程进入等待状态。

  只能从empty状态进入writing状态,一旦进入writing状态后,其它线程都不能写操作,任何写或读请求都必须等到这个线程完成写操作为止,而且waiting状态中的进程也必须一直等到写操作结束。完成操作后,返回到empty状态,发送一个通知信号,等待的线程将得到服务。

  下面实现了这个信号标志

  class Semaphore{

  final static int EMPTY=0;

  final static int READING=1;

  final static int WRITING=2;

  protected int state=EMPTY;

  protected int readCnt=0;

  public synchronized void readLock(){

  if(state==EMPTY){

  state=READING;

  }

  else if(state==READING){

  }

  else if(state==WRITING){

  while(state==WRITING){

  try {wait();}

  catch(InterruptedException e){;}

  }

  state=READING;

  }

  readCnt++;

  return;

  }

  public synchronized void writeLock(){

  if(state==EMPTY){

  state=WRITING;

  }

  else{

  while(state!=EMPTY){

  try {wait();}

  catch(InterruptedException e) {;}

  }

  }

  }

  public synchronized void readUnlock(){

  readCnt--;

  if(readCnt==0){

  state=EMPTY;

  notify();

  }

  }

  public synchronized void writeUnlock(){

  state=EMPTY;

  notify();

  }

  } |||   现在是测试信号标志的程序:

  class Process extends Thread{

  String op;

  Semaphore sem;

  Process(String name,String op,Semaphore sem){

  super(name);

  this.op=op;

  this.sem=sem;

  start();

  }

  public void run(){

  if(op.compareTo("read")==0){

  System.out.println("Trying to get readLock:"+getName());

  sem.readLock();

  System.out.println("Read op:"+getName());

  try {sleep((int)(Math.random()*50));}

  catch(InterruptedException e){;}

  System.out.println("Unlocking readLock:"+getName());

  sem.readUnlock();

  }

  else if(op.compareTo("write")==0){

  System.out.println("Trying to get writeLock:"+getName());

  sem.writeLock();

  System.out.println("Write op:"+getName());

  try {sleep((int)(Math.random()*50));}

  catch(InterruptedException e){;}

  System.out.println("Unlocking writeLock:"+getName());

  sem.writeUnlock();

  }

  }

  }

  public class testSem{

  public static void main(String argv){

  Semaphore lock = new Semaphore();

  new Process("1","read",lock);

  new Process("2","read",lock);

  new Process("3","write",lock);

  new Process("4","read",lock);

  }

  }

  testSem 类从process类的四个实例开始,它是个线程,用来读或写一个共享文

  件。Semaphore类保证访问不会破坏文件,执行程序,输出结果

  Trying to get readLock:1

  Read op:1

  Trying to get readLock:2

  Read op:2

  Trying to get writeLock:3

  Trying to get readLock:4

  Read op:4

  Unlocking readLock:1

  Unlocking readLock:2

  Unlocking readLock:4

  Write op:3

  Unlocking writeLock:3

  从这可看到,

  2.5死锁以及怎样避免死锁:

  为了防止数据项目的并发访问,应将数据项目标为专用,只有通过类本身的实例方法的同步区访问。为了进入关键区,线程必须取得对象的锁。假设线程要独占访问两个不同对象的数据,则必须从每个对象各取一个不同的锁。现在假设另一个线程也要独占访问这两个对象,则该进程必须得到这两把锁之后才能进入。由于需要两把锁,编程如果不小心就可能出现死锁。假设第一个线程取得对象A的锁,准备取对象B的锁,而第二个线程取得了对象B的锁,准备取对象A的锁,两个线程都不能进入,因为两者都不能离开进入的同步块,既两者都不能放弃目前持有的锁。避免死锁要认真设计。线程因为某个先决条件而受阻时,如需要锁标记时,不能让线程的停止本身禁止条件的变化。如果要取得多个资源,如两个不同对象的锁,必须定义取得资源的顺序。如果对象A和B的锁总是按字母顺序取得,则不会出现前面说道的饿死条件。

  三Java多线程的优缺点

  由于JAVA的多线程功能齐全,各种情况面面具到,它带来的好处也是显然易见的。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX,Windows,Macintosh 等),在开发难易程度和性能上都比单线程要好。当然一个好的程序设计语言肯定也难免有不足之处。由于多线程还没有充分利用基本OS的这一功能。这点我在前面已经提到,对于不同的系统,上面的程序可能会出现截然不同的结果,这使编程者偶会感到迷惑不解。希望在不久的将来JAVA的多线程能充分利用到操作系统,减少对编程者的困惑。我期待着JAVA会更好。

编辑推荐:

下载Word文档

温馨提示:因考试政策、内容不断变化与调整,长理培训网站提供的以上信息仅供参考,如有异议,请考生以权威部门公布的内容为准! (责任编辑:长理培训)

网络课程 新人注册送三重礼

已有 22658 名学员学习以下课程通过考试

网友评论(共0条评论)

请自觉遵守互联网相关政策法规,评论内容只代表网友观点!

最新评论

点击加载更多评论>>

精品课程

更多
10781人学习

免费试听更多

相关推荐
图书更多+
  • 电网书籍
  • 财会书籍
  • 其它工学书籍
拼团课程更多+
  • 电气拼团课程
  • 财会拼团课程
  • 其它工学拼团
热门排行

长理培训客户端 资讯,试题,视频一手掌握

去 App Store 免费下载 iOS 客户端