




下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
10线程JavaJava语言的一个很重要的特点。本章将首先介绍线程的概念以及使用线程的优点,然后详细介绍如何通过继承ThreadRunnableJava题例如线程调度、线程同步等问题我们也在本章中加以讨论。本章最后介绍了线程组的的概念及应用。ThreadRunnable多线JavaJavaAPI多线程的程序。多线程能够有效的提高程序的处理效率。本章从多线程的概念和多线程的优点开始讲述。Java程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的空间,而是和所属进程中的其它线程共个空间,这使得线程间的通信远较进程简单。果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度,同步等问题,将在本章中加以探讨。Java语言产生前,传统的程序设计语言的程序同一时刻只能单任务操作,效率非常低,例如程序往往在接收数据输入时发生阻塞,只有等到程序获得数据后才能继续运行。随着Internet的迅猛发展,这种状况越来越不能让人们忍受:如果网络接收数据阻塞,程序就处于等待状态而不继续任何操作,而这种阻塞是经常会碰到的,此时CPU资源被白白的闲置起来。如果在程序中能够同时处理多个任务,则能充分的利用CPU资源。多线程是Java语言的一个很重要的特点。在一个Java程序中,我们可以同时并行运行多个相对独立的线程,例如,我们如果创建一个线程来进行数据输入输出,而创建另一个线程在进行其它的数据处理,如果输入输出线程在接收数据时阻塞,而处理数据的线程仍然在运行。多Java提供了类java.lang.Thread来方便多线程编程,这个类提供了大量的方法来方便而Runnable接口在无法继承Thread类的情况下提供另外一种实现线程的方法。下面我们java.util.Timer和java.util.TimerTask可以使开发人员很容易实现简单的任务。在J2SDK1.3中引入这两个类之前,开发人员必须编写自己的调度程序,这需要花费很大精力来处理线程和复杂的Object.wait()方法。不过,Java计时器没有足够的能力来满足许多应Timer在run()方法中我们编写我们要做的事情,在这个例子中即打印“Time’sup!”,然后调用cancel()方法取消计时器。在Reminder类的构造函数中,我们创建一个Timer的对象,然后调用schedule()方法:timer.schedule(newRemindTask(),seconds*1000毫秒后启动任务(newRemindTask())。因此,这个例子将在运行5秒后打印出“Time’sup”。importjava.util.Timer;importjava.util.TimerTask;publicclass{TimerpublicReminder(int{timer=newtimer.schedule(newRemindTask(),}classRemindTaskextends{publicvoidrun(){System.out.println("Time'sup!");}}publicstaticvoidmain(String{newReminder(5);System.out.println("Taskscheduled.");}}1秒钟重复做一件事情,即打印警告信息“Alert!”,重复三次后打印“Time’sup!”并且退出程序,注意schedule()方法的调用参数:timer.schedule(newRemindTask(),0,1*1000RemindTaskrun()importjava.util.*;publicclassAlert{TimerpublicAlert()timer=newTimer();timer.schedule(newRemindTask(),}classRemindTaskextends{intnumWarning=publicvoidrun(){if(numWarning>0)}elseSystem.out.println("Time'sup!");}}}publicstaticvoidmain(Stringargs[]){newAlert();}}Time'sup!Thread如果要做的不是一个简单的重复作业,则Timer就显得力不从心了。这时java.lang.ThreadThreadJavaThreadThreadthread1=newThread();从这个例子,我们可以通过Thread()构造方法创建一个线程,并启动该线程。事实上,启动线程将启动线程的run()方法,而Thread类中的run()方法没有任何操作语句,所以这run()方法。Java中通常有两种方式定义run()方法:通过定义一个Thread类的子类,在该子类中覆盖run()方法。Thread子类的实例对象就是一个线程,显然,该线程有我们自己设计的线程体run()方法,启动线程就启动了子类中重写的run()方法。通过Runnable接口,在该接口中定义run()方法的接口。所谓接口跟类非常类似,主类,在该类中定义自己的run()方法,然后以该类的实例对象为参数调用Thread类的构造线程的所有活动都是通过线程run()方法来实现的。在一个线程被建立并初始化以后,Java的运行时系统就自动调用run()方法,正是通过run()方法才使得建立线程的目的得以下面的程序说明了如何通过继承Thread类实现线程。在例子中myThread类继承了ThreadThreadrun()main()myThread的实例并调用start()方法启动这个线程。完整的代码如下:classmyThreadextends{publicvoidrun()for(inti=0;i<10;{ o"+}}}publicclassthreadSample{publicstaticvoidmain(Stringargs[]){myThreadthread=newmyThread();thread.start();}}下面我们再实现一个稍微复杂一点的例子。这个例子中,myThread类的run()方法打印一些信息并且进入睡眠,睡眠时间是随时的。在main()方法中将创建两个myThread类classmyThreadextends{myThread(Stringstr){}publicvoidrun()for(inti=0;i<10;{System.out.println(getName()+": o"+i);try{sleep((int)(Math.random()*}catch(InterruptedExceptione)}}}publicclassmultiThreadSample{publicstaticvoidmain(Stringargs[]){myThreadthread1=newmyThread("Thread1");myThreadthread2=newmyThread("Thread2");thread1.start();}}每个线程体都有一个名字,Thread缺省的名称是一个短线连字符和一个零开始的数字符号,如果不接受Java的缺省线程名称则可以选择使用自己的。为了能够自定义名称,ThreadnamemultiThreadSample中,我们对线程体的构造myThreadthread1=newmyThread("Thread1");myThreadthread2=newmyThread("Thread2");myThreadthread1thread2分别命名为“Thread1”和“Thread2”,除此之外,Thread类还提供了方法setName()用于设置一个线程的名字。Thread也提供一个System.out.println(getName()+" o"+multiThreadSample.javaThreadmyThreadthread1=newmyThread("Thread在启动这个线程之前,这个线程处于创建状态,它仅仅是一个空的线程对象,系统不为它分配资源。处于这种状态时只能启动或终止该线程,调用除这两种以外的其它方法都会失败并且会引起状态处理。其中start()方法产生了运行这个线程所需的系统资源,安排其运行,并调用线程体的run()方法,这样就使得该线程处于可运行(Runnable)状态。需要注意的是这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java的运行系统必须实现调度来保证这些线程共享处理器。但是在大多数情况下,可运行状态也就是运行中,当一个线程正在运行时,它是可运行的,并且也是当前正运行的线程。当下面四种情况发生时,线程就进入不可运行状态:调用了sleep()方法;调用了suspend()方法;为等候一个条件变量,线程调用wait()方法;输入输出流中发生线程阻塞。multiThreadSample.javarun()sleep()trysleep((int)(Math.random()*}catch(InterruptedExceptione)在这个例子中我们可以看到通过调用sleep()方法使得myThread线程休止了(int)(Math.random()*1000)毫秒,这时即使处理器空闲,也不能执行该线程。sleep()调用结束以后,myThread又成为可运行的了。如果线程处于睡眠状态中,sleep()方法中的参数为休息时间,当这个时间过去后,线程即为可运行的;notify()或notifyAll()方法I/OI/O指令将结束这种不可运行状态。自然撤消是指从线程的run()方法正常退出,而调用stop()方法也可以停止当前线程。在Thread 类的程序接口中提供了isAlive()方法,如果线程已被启动并且未被终止,那么isAlive()返回true。如果返回false,则该线程是新创建或是已被终止,若返回true,则该线程是可运行或是不可运行的,但是我们不能通过isAlive()方法的返回值判断这个线程是Join()方有些情况下,当新创建一个线程并启动它之后,希望等待这个线程结束之后再作其他run()函数的功能是打印5main()函数希望5行字符之后打印“Threadfinished”的信息。程序代码如下:classmyThreadextends{publicvoidrun()for(inti=0;i<5;{ o"+}}}publicclassthreadSample2{publicstaticvoidmain(Stringargs[]){myThreadthread=newmyThread();thread.start();System.out.println("Threadfinished.");}}Threadfinished.为什么会这样?因为程序中的main函数执行thread.start后马上就接着执行System.out.println("Threadfinished."),而这时threadisAlive()方法来判线程是否结束,只有结束后才继续main()函数的工作。例如,上面的程序可以改成以下这样:classmyThreadextends{publicvoidrun()for(inti=0;i<5;{ o"+}}}publicclassthreadSample2{publicstaticvoidmain(Stringargs[]){myThreadthread=new}catch(InterruptedExceptionSystem.out.println("Thread}}main()isAlive()函数thread的状态,如果thread函数还没结束,则主线程调用Thread.sleep(10)休眠10毫秒。可以看到,这次打印的信息是正确的。事实上,while循环、isAlive()方法和sleep()方法结合起来是很有用的,Java语言提供了join()方法用于等待一个线程的停止。join()方法又三个形式:join(),join(longmillis)和join(longmillis,intnanos)。后两join()方法其实上在等待线程停止的过程中加入了超时的概念,如果超过参数所指定的时间线程还没退出,则join()方法返回,调用join()的线程继classmyThreadextends{publicvoidrun()for(inti=0;i<5;{ o"+}}}publicclassthreadSample3{publicstaticvoidmain(Stringargs[]){myThreadthread=newmyThread();thread.start();}catch(InterruptedExceptionSystem.out.println("Thread}} Thread类提供了一个静态方法currentThread()可以用于返回正在运行的线程体。这个方法有时候是很重要的。例如下面这个程序例子中,myThread线程体的run()方法调foodoSomething()方法打印一些信息,这些信息包括调用线程的名字,但是foo类中并没有保存对线程题的,因此我们只能通过Thread.currentThread().getName()得到调用者的名字。这个例子只是简单的说明了currentThread()方法的必要性,因此只是调用getName()得到当前运行线程的名字。事实上,在实际应用中,调用currentThread()方法得classstaticvoidfor(inti=0;i<10;i++)System.out.println(Thread.currentThread().getName()+": o"+}}classmyThreadextendsThreadpublicvoid{}}publicclassThreadAccess{publicstaticvoidmain(Stringargs[]){myThreadthread1=newmyThread();myThreadthread2=newmyThread();thread1.start();}}ThreadactiveCount()enumerate()分别用于得到当前同一线程组classmyThreadextends{publicvoidrun(){}catch(Exception}}publicclassThreadAccess2{publicstaticvoidmain(Stringargs[]){myThreadthread1=newmyThread();myThreadthread2=newmyThread();thread1.start();intn=Thread.activeCount();System.out.println("Activethreadscount:"+n);Threadtarray[]=newThread[Thread.activeCount()];System.out.println("Activethreads:");for(inti=0;i<tarray.length;i++)}}Activethreadscount:3Activethreads:Thread-需要注意的是,在一般情况下,当重申一个数组时不要依靠activeCount()的返回值。如果这样做,程序将冒掷出一个NullPointerException对象的风险。为什么呢?在调用activeCount()和enumerate(Thread[]thdarray)之间,一个或线程可能结束。结果,enumerate(Thread[]thdarray)能够少数线程进它的数组。因此,仅考虑将activeCount()的返回值作为数组可能大小的最大值。同样,考虑将enumerate(Thread[]Runnable言不允许一个类继承多个类,如果某个类已经成继承其他类,那么它将不可以继承Thread类。幸运的是,Java的设计者已经不可能创建Thread子类的情形总会发生的。这导致产生java.lang.Runnable接口和带Runnable参数的ThreadThread(RunnableRunnable接口了唯一一个方法:voidrun()。这个方法和Thread的run()方法一样子句包含进类头和提供一个适当的run()方法实现接口。在执行时间,程序代码能从那个类创建一个对象或runnable并将runnable的传递给一个适当的Thread构造器。构造器和Thread对象一起存贮这个并确保一个新线程在调用Thread对象的start()方法后调用runnablerun()方法。class void}
classMyRunnableextendsfooimplements{voidfor(inti=0;i<5;{System.out.println(Thread.currentThread().getName()+" o"{}catch(InterruptedExceptione){e.printStackTrace();}}publicvoidrun{}}publicclassRunnableDemo{publicstaticvoidmain(Stringargs[]){MyRunnablethread1=newMyRunnable();MyRunnablethread2=newMyRunnable();newnew}}RunnableDemoRunnableDemo,fooMyRunnableRunnableDemo通过MyRunnable对象驱动应用程序。fooMyRunnable组成了一个基于foo层次的类。foo是抽象的因为它提供一个抽象的printInfo()方法。MyRunnable类扩展foo。由于MyRunnable继承了foo类,因此它不能同时继承Thread类,因此只能通过让之实现RunnableRunnableDemomain()MyRunnable的对thread1thread2ThreadThreadstart()方法启MyRunnablethread1=newMyRunnable();MyRunnablethread2=newMyRunnable();newThread(thread1).start();new线程取CPU时间的机会。Java语言提供了设计线程优先级的方法。Java线程的优先级可由用户来设置,线程的优先级是由一个整数表示,这个整数越大则线程的优先级越大。Java优先级的范围在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间。缺省情况下线程的优先级是Thread.NORM_PRIORITY。Thread类提供了setPriority()和getPriority()方法来设置和优先级。Java虚拟机是抢占式的,它能保证运行优先级最高的线程。在Java一个线程的优先级改为最高,那么他将取代当前正在运行的线程,除非这个线程结束运行或者进入休眠状态,否则将一直占用所有的处理器的时间。下面是一个模仿赛跑的程序。Runner类的run()方法中对自身的私有成员step做自增的操作然后进入睡眠1毫秒Race类的main()方法中创建了Runner类的两个对象runner[0]和runner[1],分别把它们的优先级设置为Thread.MIN_PRIORITY Thread.MAX_PRIORITY,然后调用start()方法启动它们。main()方法每隔5秒钟就查询一下这两个对象的step值,然后分别打印这两个对象的step值。可以看到,这个赛跑程序是的,因为runner[1]的优先级比较高,所以它获取CPU的机会就大,可以预料,在一段时间后,runner[1]steprunner[0]stepclassRunnerextends{privateintstep=publicvoid{while(step<50000){step++;}catch(Exception}}publicgetStep(){return}}publicclasspublicstaticvoidmain(Stringargs[]){Runnerrunner[]={ newwhile(runner[0].isAlive()&&runner[1].isAlive()){try{}catch(ExceptionSystem.out.println("Runner0:"+runner[0].getStep());System.out.println("Runner1:"+}}}RunnerRunner0:RunnerRunner1:一个具有更高线程优先级的线程进入可运行状态这个线程结束运行CPU时间分成一个一个的时间片,片计数到时后,系统选择另一个线程并分给它时间片,让其投入运行,如此循环反复。当这些条件的某个为真时,另外一个线程将有机会得到CPU下面的程序说明了这种情况,runner[0]和runner[1]的优先级都设为2classRunnerextends{privateintstep=publicvoid{while(step<50000){step++;}catch(Exception}}publicgetStep(){return}}publicclasspublicstaticvoidmain(Stringargs[]){Runnerrunner[]={ newwhile(runner[0].isAlive()&&runner[1].isAlive()){try{}catch(ExceptionSystem.out.println("Runner0:"+runner[0].getStep());System.out.println("Runner1:"+}}}Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是程对象创建之前调用线程对象的setDaemon()例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时和管理系统中的可回收资源。只有当创建中的线程是daemon时,这个新的守护线程才会自动成为daemon。从调度的角度来说,守护线程与用户线程是类似的。Java虚拟机仅仅只在用户线程退出后才检查某个特定的线程是不是守护线程。如果Java虚拟机发现内存中只有守护线程而没有用户线程时,它会自动退出。守护线程的存在只是为了服务于用户线程。回收线程是JavavoidsetDaemon()才能被调用。当Java虚拟机启动时,通常只有一个非守护线程(在有些已作指定的类中,这一线典型地调用名为main的函数。Java虚拟机继续执行线程,直到以下任何一种情况类Runtime所有的非守护线程都因为而终止。这些原因包括:运行调用返回或是发线程线程同步是多线程编程中很重要的技术。在实际应用中,几乎所有多线程都必须考虑同步的问题。本小节通过对生产者-消费者问题的讨论,探讨线程同步的方法。最后还介绍了饥饿和死锁的概念。前面所提到的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部的资源或方法,也不必关心其它线程的状态或行为。但是经常有一些同时运行的线程需要共享数据,例如一个线程向文件写数据,而同时另一个线程从同一文件中数据,因此就必须考虑其它线程的状态与行为,这时就需要实现同步来得到预期结果。Java的应用程序中,生产者线程向文件中写数据,消费者从文件中读下面我们通过一个程序来模拟生产者-消费者问题。在这个例子中,生产者产生从“Book0”到“Book9”的10个字符串,代表10本书,将它们在一个BookCase的对sleep()0100publicclassProducerextends{privateBookCasebookcase;privateintnumber;publicProducer(BookCaseb,int{bookcase=b;this.number=number;}publicvoidrun()for(inti=0;i<10;{Stringbook="Book"+i;System.out.println("Producer"+this.number+"put:"+trysleep((int)(Math.random()*}catch(InterruptedExceptione){}}}消费者线程则不断地从BookCasepublicclassConsumerextends{privateBookCasebookcase;privateintnumber;publicConsumer(BookCaseb,int{bookcase=b;this.number=number;}publicvoid{Stringbook=for(inti=0;i<10;{book=bookcase.get();System.out.println("Consumer"+this.number+"got:"+}}}在这个程序中,我们通过一个producerConsumerTestpublicclass{publicstaticvoidmain(String[]{BookCaseb=newBookCase();Producerp1=newProducer(b,1);Consumerc1=newConsumer(b,1);}}在这个例子中,生产者与消费者通过BookCase对象来共享数据。但是我们发现不论是生产者线程还是消费者线程都无法保证生产者每放入一本书,消费者就取得这本书并且只取一次。下面将说明没有同步的情况下有可能发生的事情。Consumer1got:Book3Producer1put:Book4Producer1put:Book5Consumer1got:Book5另外一个问题是:如果消费者比生产者快,那么消费者就有可能在生产者还没放入新时CubbyHole多次。这种情况下消费者将多次取到同一本书,例如:Producer1put:Book4Consumer1got:Book4Consumer1got:Book4Producer1put:Book5Consumer连续取到两次“Book4Producer才放入“Book5上面两种输出结果都不是我们所希望的那样:生产者每放入一本书,消费者就取这本书。象这种异步执行的多线程,由于希望同时进入同一对象中而发生错误结果的情况称为竞争条件(racecondition)。为了避免上述情况发生,就必须使生产者线程向BookCase中数据和消费者线程从BookCase中取数据同步起来。为达到这一目的,必须采取两种措施:不能让生产者和消费者同时BookCase在Java线程中,我们可以通过锁住一个对象来做到这一点。档一个对象被某个线程锁定后,如果有第二个线程调用同步的方法方位这个对象时,第二个线程就会阻塞,直到对象锁被释放。生产者和消费者能够彼此之间进行协调。生产者在放入新书后要通知消费者,而消费者在取出一本书后也要通知生产者。Threadwait()notify()notifyAll()方法来完成这些事情。wait()方法用于在某个条件上进行等待,而notify()和notifyAll()则用于在条件改变之后通知其他线程。以下的BookCase实现了同步,注意put()方法和get()方法。在下一小节中详细说明get()put()方法实现同步的具体过程。publicclass{privateStringprivatebooleanavailable=publicsynchronizedString{while(available==false){{}catch(InterruptedExceptione){}available=false;returnBook;}publicsynchronizedvoidput(String{while(available==true){{}catch(InterruptedExceptione){}Book=newBook;available=true;}}运 ProducerConsumerTest,可以看到,输出的结果是正确的11111111111111111111我们把一次只允许一个线程使用的共享资源称为临界资源(CriticalResource,而在每个进程中临界资源的程序段称为临界区(CriticalSection。对象通过synchronized关键字来线程必须获得监视器才能进行对自己的。在我们的生产者-消费者例子中,BookCase的put()get()方法是临界区。在生产和get()方法必须用synchronized关键字标志起来。如下所示:publicclass{privateStringprivatebooleanavailable=false;publicsynchronizedStringget(){}publicsynchronizedvoidput(StringnewBook)}}BookCase类的put()方法和get()方法都标志有synchronized关键字,则系统将给BookCase类的每一个对象都赋予一个唯一的锁。无论什么时候,只要某个线程进入标有synchronized关键字的片断,这个对象就被该线程锁住。如果在此同时其他线程也准备调用同一个对象标有synchronized的方法,它将被暂停直到当前线程离开synchronized代码所以,生产者调用BookCase的put()方法时,它就锁住BookCase,以消费者它的get()方法:publicsynchronizedvoidput(StringnewBook)//生产者获//生产者释}put()方法返回时,生产者释放锁类似的,消费者调用get()方法时,它就锁住BookCase以生产者调用put()方法publicsynchronizedStringget()//消费者获//消费者释}Javawait()、notify()和notifyAll()当线程在继续执行前需要等待一个条件时,仅有synchronized关键字是不够的。虽然synchronized关键字并发一个对象,但它没有实现线程之间的通讯。为此,Objectwait()、notify()notifyAll()方法。获得对象锁的线程可以通过调用该对象的wait()方法主动释放锁,等待在该对象的线程等待队列上,此时其他线程可以得到锁从而该对象,之后可以通过调用notify()notifyAll()方法来唤醒先前因调用wait()方法而等待的wait()notify()notifyAll()方法的调用都是根据一定的条件来进行的。notifyAll()notify()方法的区别的是notifyAll()唤醒所有等待的线程,这些线程中的一个经过竞争进入临界区,其它的继续等待。BookCase有两个变量:BookBookCaseavailable指示当前内容是否可以取出。只有当available是真时,消费者才能取数据。为实现两线程同步,消费者接收数据的前提是 为真,即数据单元内容不空生产者发送数据的前提是数据单元内容为空。基BookCase的实现可能如下:publicsynchronizedString{if(available==true){available=false;return}}publicsynchronizedvoidput(String{if(available==false){available=true;Book=}}但是这样的实现却是不对的。让我们考虑get()方法,当生产者还没放入书、available为假时,get()什么都没有做。同样,当消费者没取走书、available为真时,put()什么都没做。很自然的,我们会想到:消费者应该等待生产者放入书,而生产者放入书后通知消费者在下面这个实现中,我们通过调用对象的notifyAll()和wait()方法来实现上述的功能publicclass{privateStringprivatebooleanavailable=publicsynchronizedString{while(available==false){{}catch(InterruptedExceptione){}available=false;returnBook;}publicsynchronizedvoidput(String{while(available==true){{}catch(InterruptedExceptione){}Book=newBook;available=true;}}get()个while是available果available为话,消费者就知道生产者还没有产生新的数据,将继续等待。while()循环中调用wait()方法,put()方法调用notifyAll()while()循环。put()availablewait()可能出现的问题是:在get()方法的开始,available为假,消费者线程必须等待生产者发送数据,那么,如果消费者占据锁,生产者如何发送呢?同样,若数据未被消费者取走,而生产者占据锁,消费者怎样获得数据呢?在Java中是这样处理的:当一个线程进入等待状态后,锁就会自动释放,而当它被唤醒后,该线程又占据锁。这样就使等待的线程有机会进入临界区。为了进入临界区,线程必须取得对象的锁。假设现在有两个线程要独占的两个对象:对象A和对象B,任一个线程要其中一个对象都需要获得对象锁。如果在某一个时刻,第一个线程已经持有对象A的锁并正在企图获得对象B的锁,而同时,第二个线程已经持有对象B的锁并正在企图获得对象A的锁。那么它们各自占有对象A和对象B的锁,并且死锁问题的一个著名问题是哲学家问题:一个房间内有5个哲学家,他们的生活就是思考和进食。房间里有一张圆桌,中间放着一盘通心粉(假定通心粉无限多。桌子周围放有五把椅子,分别属于五位哲学家每两位哲学家之间有一把叉子,哲学家进食时必须同时使用左右两把叉子。如果在某个时间,哲学家同时拿起他们左边的叉子,而拿起叉子后就不主动放下,那么所有的哲学家都会在们右边叉子的过程中。为了避免死锁,一般应该遵循以下一些原则:资源归类,将各种资源归入若干个不同的资源类(ResourceGroup)中。资源排序,在不同资源类之间规定次序,对不同资源类中的资源采用线性按序申请的方法。针对性优化,对同一资源类中的资源,采用适当的方法。如前面所述的问题,如果对象AB的锁总是按字母顺序取得,则不会出现死锁。在编写多线程代码时,死锁是最难处理的问题之一。因为死锁可能在最意想不到的地方publicintsumArrays(int[]a1,int[]a2){intvalue=0;intsize=a1.length;if(size==a2.length){synchronized(a1){ synchronized(a2){//Bfor(inti=0;i<size;i++)value+=a1[i]+a2[i];}}}return}这段代码在求和操作中两个数组对象之前正确地锁定了这两个数组对象。它形式简短,编写也适合所要执行的任务;但不幸的是,它有一个潜在的问题。这个问题就是它埋下了死锁的,除非您在不同的线程中对相同的对象调用该方法时格外。要查看创建两个数组对象,ArrayA和ArrayB。线程1用下面的调用来调用sumArrayssumArrays(ArrayA,线程2用下面的调用来调用sumArrayssumArrays(ArrayB,线程1开始执行sumArrays方法并在//A处获得对参数a1的锁,对于这个调用而言,它就是对ArrayA对象的锁。然后在//B处,程1获得对ArrayB的锁之前被抢先线程2开始执行sumArrays方法并在//A处获得对参数a1的锁,对于这个调用而言,它就是对ArrayB对象的锁。2在//Ba2ArrayA对象的锁。因为这个锁当前由线程1持有,所以线程2被阻塞。线程1开始执行并在//Ba2ArrayB对象的锁。因为这个锁当前由线程2持有,所以线程1被阻塞。避免这种问题的法是让代码按固定的全局顺序获取锁。在本例中,如果线程1多线程代码的程序员在调用那些锁定作为参数传入的对象的方法时需要格外。在您遇到确定正确的锁定顺序。只要即将锁定的所有对象都支持锁定顺序表示法,并且获取锁的代码在对象中嵌入锁定顺序的缺点是,这种实现将使内存需求和运行时成本增加。另外,在上例中应用这一技术需要在数组中有一个包装对象,用来存放锁定顺序信息。例如,试考虑下面的代码,它由前面的示例修改而来,其中实现了锁定顺序技术:classprivatestaticlongnum_locks=0;privatelonglock_order;privateint[]publicArrayWithLockOrder(int[]{arr=a;synchronized(ArrayWithLockOrder.class){ 锁数加1lock_order=num_locks;//为此对象实例设置唯一的lock_order}}publiclong{return}publicint[]{return}}classSomeClassimplements{publicintsumArrays(ArrayWithLockOrderArrayWithLockOrder{intvalue=ArrayWithLockOrderfirst= //保留数组的一ArrayWithLockOrderlast= //intsize=a1.array().length;if(size==a2.array().length){if(a1.lockOrder()>a2.lockOrder())// first=a2;last=a1;}synchronized(first //synchronized(last)int[]arr1==a1.array();int[]arr2==a2.array();for(inti=0;i<size;i++)value+=arr1[i]+}}}return}publicvoidrun()}}在第一个示例中,ArrayWithLockOrder类是作为数组的一个包装提供的。每创建该类的一个新对象,该类就将staticnum_locks变量加1。一个单独的lock_order实例变量被num_locksstatic变量的当前值。这可以保证,对于该类的每个对象,lock_order变量都有一个独特的值。lock_order实例变量充当此对象相对于该类的其他对象的锁定顺序请注意,staticnum_locks变量是在synchronized语句中进行操作的。这是必须的,因为对象的每个实例共享该对象的static变量。因此,当两个线程同时创建ArrayWithLockOrder类的一个对象时,如果操作staticnum_locks变量的代码未作同步处ArrayWithLockOrder类的每个对象,lock_order变量都有一个独特的值。此外还更新了sumArrays方法,以使它包括确定正确锁定顺序的代码。在请求锁之前,实现嵌入的锁定顺序需要投入的工作,使用的内存,并会延长执行时间。但是,如果代码中可能存在这些类型的死锁,这样做是值得的。如果无法承受额外的内存和执行开销,或者不能接受nu_lockslock_order重新开始的可能性,则在建立锁定对象的预定义顺序时应该仔细斟酌。线程组顾名思义是对线程进行分组。Java中规定,线程组中可以包括若干线程和线程每个线程都隶属于唯一一个线程组,这个线程组程创建时指定并程的整个生ThreadGroup类型参数的Thread类构造函数来指定线程属的线程组,若没有指定,则线程缺省地隶属于名为system的系统线程组。在Java在Java中,除系统线程组外的每个线程组又隶属于另一个线程组,你可以在创建线程组时指定其所隶属的线程组,若没有指定,则缺省地隶属于系统线程组。这样,所有线程组组成了一棵以系统线程组为根的树。Java允许我们对一个线程组中的所有线程同时进行操作,比如我们可以通过调用线程组线程组机制的另一个重要作用是线程安全。线程组机制允许我们通过分组来区分有不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来支持不对等安全措施的采用。Java的ThreadGroup类提供了大量的方法来方便我们对线程组树中的每一个线程组以及线程组中的每一个线程进行操作。importjava.lang.*;importjava.io.*;publicclasspublicstaticvoidmain(String[]ThreadGroupgrp1=newThreadGroup("Group1");ThreadGroupgrp2=newThreadGroup("Group2");System.out.println("Group1:"+grp1.toString());Threadthread1=newThread(grp1,newThreadGroupparent=grp1.getParent();System.out.println("Group1'sparent:"+parent);System.out.println("ParentGroup'sinfo:");System.out.println("ActiveGroupCountinParentGroup:+parent.activeGroupCount());System.out.println("ActiveCountinParentGroup:"+parent.activeCount());System.out.println("Currentthread'sGroup:+}}classAThreadextendsThread{publicAThread(){}publicvoidSystem.out.println("IamThread:"+}}程序工作是这样的:先创建两个ThreadGroupThreadGroup对象中创建一个线程。然后就是打印一些信息。先输出了第一个ThreadGroup对象的有关信息,再显示其父线程组的信息。然后调用list()方法打印该父线程组中各级成员列表。然后调用activeGroupCount()方法和activeCount()方法统计了列表中线程组与线程的数目,之后是当前线程的所程组描述,最后是关于一个线程的信息。程序的开始我们创建了线程组对象,构造函数的参数为线程组名。ThreadGroup类另publicThreadGroup(ThreadGroupparent,StringpublicThread(ThreaqdGroupgroup,RunnableAThreadAThreadThreadThreadRunnable接口,AThreadRunnabletarget了。这里我们再解释一下为什么会有Thread-0和Thread-1两个线程。其中一个是我们创建并使之运行的thread1,另一个就是程序中newAThread()的结果:Threadthread1=newThread(grp1,newGroup1:java.lang.ThreadGroup[name=Group1,maxpri=10]Group1'sparent:java.lang.ThreadGroup[name=main,maxpri=10]ParentGroup'sinfo:ActiveGroupCountinParentGroup:2ActiveCountinParentGroup:Currentthread'sGroup:java.lang.ThreadGroup[name=main,
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年医疗行业大数据隐私保护合规政策与实施指南报告001
- 2025年云计算服务模式创新与行业应用解决方案竞争格局研究报告
- 2025年元宇宙社交平台营销策略与品牌合作报告
- 2025年医院电子病历系统优化在医院信息化建设中的数据生命周期管理报告001
- 2025年医药行业研发创新与技术转移报告
- 2025年医药企业研发外包(CRO)在临床试验安全性评价中的应用报告
- 2025年医药企业研发外包(CRO)模式下的新药研发项目管理工具比较与选择报告
- 绿色建筑认证体系在绿色酒店管理中的应用与创新报告
- 2025年医药流通企业供应链创新与成本控制模式研究报告
- 农村消费金融市场细分领域发展现状与机遇研究报告
- 北师大版(2024)七年级上册生物期末复习全册考点背诵提纲
- 2025年湖南中考生物试题及答案
- 混凝土站销售管理制度
- 山东省威海市实验中学2025届七下英语期末达标检测试题含答案
- 第七中学高二下学期5月月考语文试题(含答案)
- 2025至2030中国旋转密码挂锁行业发展分析及前景趋势与投资报告
- 苏教版八年级下物理期末考试试题(含三套试卷)
- 2025年河北省中考麒麟卷地理(三)及答案
- 河南天一大联考2025年高二下学期期末学业质量监测英语试题
- 国际学校员工管理制度
- 农药经营许可证培训考试题库及答案
评论
0/150
提交评论