![1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程【推荐-】_第1页](http://file4.renrendoc.com/view/c62f05e0c822c9f040403c55b8e01e90/c62f05e0c822c9f040403c55b8e01e901.gif)
![1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程【推荐-】_第2页](http://file4.renrendoc.com/view/c62f05e0c822c9f040403c55b8e01e90/c62f05e0c822c9f040403c55b8e01e902.gif)
![1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程【推荐-】_第3页](http://file4.renrendoc.com/view/c62f05e0c822c9f040403c55b8e01e90/c62f05e0c822c9f040403c55b8e01e903.gif)
![1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程【推荐-】_第4页](http://file4.renrendoc.com/view/c62f05e0c822c9f040403c55b8e01e90/c62f05e0c822c9f040403c55b8e01e904.gif)
![1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程【推荐-】_第5页](http://file4.renrendoc.com/view/c62f05e0c822c9f040403c55b8e01e90/c62f05e0c822c9f040403c55b8e01e905.gif)
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、1.理解进程和线程的概念,学习java中线程的使用 ;2.掌握线程的状态和生命周期、线程的调度和控制方法;3.理解多线程的互斥和同步的实现原理,以及多线程的应用,能够熟练编写关于线程、线程间的同步与通信的小程序 第9章 多线程 教学目的要求9.1 多线程的概念进程和线程进程就是在计算机中正在执行的程序即处于活动状态的程序,每一个进程都有自己独立的一块内存空间和一组系统资源,比方在Windows、Linux等操作系统中可以同时执行多个程序,这里的每一个运行的程序都是一个进程,这些程序使用的内存空间和系统资源都是独立的,并且每个程序也是为了各自的任务而运行,互不干扰。进程概念的引入是操作系统开展史
2、上的一个里程碑,正是进程技术的引入才使得计算机操作系统同时处理多个任务成为可能,这也促使了像Windows一样的多任务操作系统的出现,使计算机的运行效率在很大程度上得到了提升。在进程的根底上,线程概念后来又被提出,它使得在一个进程中同时处理多个任务成为可能。线程和进程有很多相似的特征,线程可以被看作是进程的进一步细分,也就是把进程完成的任务划分成一个个更小的子任务,每一个子任务就是一个线程,然后用细分的这些线程分别去执行。线程是基于进程的一个根本运行单位,线程同样包括一个内存入口点地址、一个出口点地址以及能够顺序执行的代码序列。但是线程与进程的重要区别在于线程不能够单独执行,它必须运行在处于活
3、动状态的进程中,线程本身的数据通常只有微处理器的存放器数据以及一个供程序执行时使用的堆栈,因此可以定义线程是程序内部的顺序代码流,也就是说线程是在进程作用域内活动的一系列指令流,所以线程也被称为轻型进程(Light Weight Process,LWP)。多线程多线程允许在程序中“并行执行多个指令流,每个指令流被称作一个线程,彼此间的执行互相独立。多线程需要操作系统的支持,WIN32平台支持多线程程序,允许程序中存在多个线程。在单CPU计算机系统中,系统把CPU的时间片按照调度算法分配给各个线程,因此各线程实际上是分时执行的,而在多CPU的计算机系统中,同一个程序的不同线程可以分配到不同的CP
4、U上去执行。多个线程的执行是并发的,也就是在逻辑上“同时,而不是物理上的“同时。如果系统只有一个CPU,那么真正的“同时是不可能的,但是由于CPU的速度非常快,用户感觉不到其中的区别。 (a)单线程程序 (b)多线程程序图9.1 单线程与多线程的比照Java中的多线程机制线程需要计算机系统的支持,并不是所有类型的计算机都支持多线程应用程序。但是由于Java引入了虚拟处理器技术,所以Java语言将线程支持与语言运行环境结合在一起,不管在任何系统下,Java语言都提供了多任务并发执行的能力,如图9.2所示。这就好比一个人在处理家务的过程中,将米放在电饭锅里后再把衣服放到洗衣机中自动洗涤,然后开始做
5、菜,等菜做好了,饭也熟了,同时衣服也洗好了。只要合理安排各个线程的运行,就可以极大地提高程序的运行效率。 对多线程的综合支持是Java语言的一个重要特色,在Java中,内置了Thread类来实现多线程,当程序引用了java.lang.Thread类,也就引入了一个Java执行环境。由图9.2可知,一个线程是由三局部组成的:虚拟处理机CPU,封装在java.lang.Thread类中,它控制着整个线程的运行,提供对多线程的支持;执行的程序代码,传递给Thread类,由Thread类控制顺序执行;程序所处理的数据,传递 给Thread类,是在代码执 行过程中所要处理的数据。虚拟CPUCode Da
6、ta图9.2 线程的组成在Java编程中,虚拟处理机CPU被封装在Thread线程类的实例之中。这样一来,有多少个需要完成的子任务线程就有多少个虚拟CPU这样的“虚拟计算机在同时运行,把一个较大的任务分割成许多较小的子任务分别地、“同时地去完成,这就是Java多线程机制。Java的线程是通过java.lang包中定义的类Thread来实现的。当生成一个Thread类的对象之后,就产生了一个线程,通过该对象实例,可以启动线程、终止线程、或者暂时挂起线程等。由于Java在语言级提供了对线程的支持,所以在Java语言中使用多线程要远比在其它语言中使用线程简单得多。9.2 线程类及其线程创立线程类 通
7、常在Java程序设计中,任何机制都是基于类的,当然线程也不例外,所以要学会Java中的多线程编程,就必需了解实现线程的Thread类。线程对象实例表示Java程序中的真正的线程,通过它可以启动线程、终止线程、挂起线程等操作,Thread类是负责向其它类提供线程支持的最主要的类,Thread类在包java.lang中定义,它的构造方法为: public ThreadThreadGroup group,Runnable target,String name; 其中,group指明该线程所属的线程组关于线程组将在后面详细讲解;target是指实际执行线程体的目标对象,它必须实现接口Runnable;
8、name为线程名,Java中的每个线程都有自己的名称,Java提供了不同Thread类构造器,允许给线程指定名称,如果name为null时,那么Java将自动为其分配一个唯一的名称。 在线程构造方法中,每个参数都可以为空,当上述构造方法的某个参数为null时,就可以得到下面的几个构造方法:public Thread;public ThreadString name;public ThreadRunnable target;public ThreadRunnable target,String name;public ThreadThreadGroup group,String name;pub
9、lic ThreadThreadGroup group,Runnable target; Thread类中也有许多有用的方法,在这里只介绍局部常用的方法:1. public static void yield;引起当前执行线程暂停,允许其他线程执行。2. public static void sleep(long millis);使当前执行的线程睡眠指定的时间。参数millis是线程睡眠的毫秒数。如果这个线程已经被别的线程中断,就会产生InterruptedException异常。3. public void start;使线程由新建状态变成可运行状态。4. public void run;如
10、果线程实例是使用实现了Runnable接口的类的实例创立的,就调用这个类的实例的run( )方法,否那么什么都不做并返回。5. public final void stop;停止杀死当前线程。6. public final void wait;返回当前线程所在的线程组中的线程的数目7. public final boolean isAlive;测试线程是否处于活动状态,即已启动,但还没有终止。8. public final void setPriority(int new);改变线程的优先级。9. public static Thread currenthread;返回当前执行线程对象的引用。
11、10. public final void notify;唤醒一个等待中的线程。11. public final void notifyAll;唤醒所有处于等待中的线程。线程的创立在Java中,每个程序至少自动拥有一个线程,称为主线程,当程序加载到内存时,启动主线程,如果需要使用其它线程,那么可以采用以下两种方式创立新的线程:一种是扩展java.lang.Thread类,用它覆盖Thread类的run( )方法;另一种是编写一个类,使之实现java.lang.Runnable接口,然后在Thread构造函数中使用它。第一种方式只能在类没有扩展其它任何类的情况下才能使用,因为Java不允许多重继
12、承。因此,如果一个类要继承其它的类,最好选用第二种方法,这样会有更大的灵活性。下面分别介绍两种创立线程的方式。1.扩展Thread类 类Thread位于java.lang包中,由于java.lang包自动被导入每个Java程序中,所以可以直接使用类Thread而无需在Java程序开始处编写import语句,也这说明了Java对线程支持的彻底性。通过这个类中的方法,可以启动、终止、中断线程以及查询、设置线程的当前状态。 使用扩展Thread类的方式创立并执行一个线程,需要执行下面4个步骤:扩展java.lang.Thread的类;用希望的执行代码来实现run方法;通过new关键字实例化该类的一个
13、新对象即一个线程;通过调用start方法启动线程。例如: public class yourThread extends Thread public run/需要以线程方式运行的代码,也就是所希望的执行代码 上面的代码完成了扩展Thread类和重写了run( )方法,为了在程序中使用线程,还需要创立一个对象并调用run( )方法,如: yourThread tt=new yourThread; tt.start; 在这里,start方法将自动调用在线程体内重写的run方法来执行线程的具体处理,当run方法执行完毕时,线程将自动结束。 下面我们将通过例9.1学习如何使用扩展Thread类的方法来
14、实现线程。例9.1 扩展Thread类创立线程。本例通过扩展Thread类来创立线程的方法,类Li9_01声明为Thread的子类,具体创立方法可以用下面的代码来描述。public class Li9_01 extends Thread int count = 1, number; public Li9_01(int num) /通过形参num接收参数传递 number = num; System.out.println(创立线程 + number); public void run( ) /重新构造run( )方法 while(true) System.out.println(线程 + nu
15、mber + :计数 + count); if (+count = 6) return; /run public static void main(String args) for(int i = 0; i 10; i+) Li9_01 tt = new Li9_01(i+1);/构造属Li9_01类的线程tt,并向线程传递参数 tt.start( );/通过调用start( )方法 /main( ) 在例9.1中构造了一个称为tt的Li9_01类的线程,并在构造后通过循环调用了start()方法屡次启动这个线程,这可以在运行后的结果中看出。需要注意的是,在Java中,Thread对象通过st
16、art()方法启动线程而不能直接调用run方法,在调用start()方法后首先进行一些初始化,然后再调用run方法启动线程。由于main()线程其实main()本身就可以看作是一个线程和tt线程的调度情况是由操作系统动态决定的,因此编译并运行这个程序,每次运行的结果不一定相同。这种方法简单明了,符合大家的习惯。但是它的缺点是:如果类已经从一个类继承,那么无法再继承Thread 类,这时如果又不想建立一个新的类,应该怎么办呢?在这里不妨来探索一种新的方法:不创立 Thread 类的子类,而是直接使用它,那么只能将方法作为参数传递给Thread类的实例,有点类似回调函数。但是 Java 没有指针,
17、只能传递一个包含这个方法的类的实例。那么如何限制这个类必须包含这一方法呢?当然是使用接口!Java 提供了接口 java.lang. Runnable 来支持这种方法。2.实现Runnable接口 利用Runnable接口创立和运行线程的编程步骤为: 第1步:定义一个Runnable接口的实现类,如MyThread,其内必须实现Runnable接口所声明的run( )方法。 定义Runnable接口的方法如下:public class yourThread implements Runnable public void run. /需要以线程方式运行的代码第2步:创立一个Thread类的对象,
18、即创立一个新线程,并用Runnable接口或者Thread类的引用变量指向它,调用Thread类带Runnable引用作为形参的构造方法,把Runnable接口实现类对象传递给Thread类的对象即传递给新线程,为新线程提供程序代码和数据。如: yourThread yourt = new yourThread; Thread tt = new Thread(yourt) ;第3步:用线程对象调用start方法启动线程。如: tt.start; 定义一个实现了Runnable接口的类,将该类的对象作为Thread类的构造方法的参数,生成的Thread对象即为想要创立的线程,这样的线程同样通过s
19、tart( )方法启动。 例9.2将例9.1改为通过接口Runnable创立线程的实例。具体创立方法可以用下面的代码来描述。/通过Runnable接口创立线程。Li9_02.javapublic class Li9_02 implements Runnable /定义Runnable接口int count= 1, number ; public Li9_02(int num) number = num ; System.out.println(创立线程 + number); public void run() /重新构造run方法while(true) System.out.println(线
20、程 + number + :计数 + count); if(+count= 6) return; /runpublic static void main(String args) for(int i = 0; i 10; i+) Li9_02 yourt = new Li9_02(i+1); Thread tt = new Thread(yourt); tt.start(); /启动线程 /main 从以上创立线程的实例可以看出,构造线程体的两种方法各自的优缺点分析如下:1.使用Runnable接口可以将CPU、代码和数据分开,形成清晰的模型;可以从其他类继承,当一个线程已继承了另一个类时,就
21、只能用实现Runnable接口的方法来创立线程;便于保持程序风格的一致性。2.扩展Thread类不能再从其他类继承,适用于单继承线程情况;编写简单,可以直接操作线程; 由以上分析可知,两种方法各有利弊,读者应该根据实际情况灵活运用。线程的状态与控制在这里需要明确的是:无论采用扩展Thread类还是实现Runnable接口的方法来实现应用程序的多线程能力,都需要在该类中定义用于完成实际功能的run()方法,这个run()方法称为线程体Thread body。按照线程体在计算机系统内存中的状态,可以将线程从产生到灭亡分为新建、就绪、运行、挂起、死亡等5种状态。图9.3描述了线程的生命周期的几种状态
22、和状态之间的转换。图9.3 线程的生命周期新建状态:线程在已经利用new关键字创立但是还未执行的这段时间里,处于一种特殊的新建状态中,此时,线程对象已经被分配了内存空间,私有数据已经被初始化,但是该线程尚未被调度。此时的线程可以被调度,变成可运行状态,也可以被杀死,变成死亡状态。就绪状态:在处于创立状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVMJava虚拟机的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的时机。 运行状态:运行状态说明线程正在运行,该线程已经拥有了对CPU的控制权。这个线
23、程一直运行到运行完毕,除非该线程主动放弃CPU的控制权或者CPU的控制权被优先级更高的线程抢占。处在运行状态的线程在以下情况下将让出CPU的控制权:线程运行完毕;有比当前进程优先级更高的线程进入可运行状态;线程主动睡眠一段时间;线程在等待某一资源。挂起状态:如果一个线程处于挂起状态,那么暂时这个线程无法进入就绪队列。处于挂起状态的线程通常需要由某些事件才能唤醒,至于由什么事件唤醒该线程,那么取决于其挂起的原因。处于睡眠状态的线程必须被挂起一段固定的时间,当睡眠时间结束时就变成可运行状态;因等待资源或消息而被挂起的线程那么需要由一个外来事件唤醒。死亡状态:正常情况下run返回使得线程死亡。调用s
24、top或destroy亦有同样效果,但是不被推荐,因为前者会产生异常,后者是强制终止,不会释放内存。到目前为止,我们仅运行了自动终止的线程,它们执行了一项任务,然后终止。但是如何控制Java线程的终止、如何停止一个线程?这就需要使用一些方法来控制线程。表9-1列出了控制线程执行的方法,可以使用表9-1中的方法创立线程并控制线程的执行。线程调用的意义在于JVM应对运行的多个线程进行系统级的协调,以防止多个线程争用有限资源而导致应用系统死机或者崩溃。Java定义了线程的优先级策略。Java将线程的优先级分为10个等级,分别用110之间的数字表示,数字越大说明线程的级别越高。相应地,在Thread类
25、中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的优先级等级分别为1、10和5。当一个线程对象被创立时,其默认的线程优先级是5。为了控制线程的运行策略,Java定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器按照线程的优先级决定哪个线程投入处理器运行。在多个线程处于就绪状态的条件下,具有高优先级的线程会在低优先级线程之前得到执行。线程调度器同样采用“抢占式策略来调度线程执行,即当前线程执行过程中有较高优先级的线程进入就绪状态,那么高优先级的线程立即被调度执行。具有相同优先级的所有线程采用轮转的
26、方式来共同分配CPU时间片。在应用程序中可以调用线程对象的setPriority()方法改变该线程的运行优先级,同样可以调用getPriority()方法获取当前线程的优先级。Java 支持10个优先级,基层操作系统支持的优先级可能要少得多,这样会造成一些混乱。因此,只能将优先级作为一种很粗略的工具使用,最后的控制可以通过使用yield()方法来完成。通常情况下,不要依靠线程优先级来控制线程的状态。 yield方法提供了一个处理时间分片问题的简单方法,如果使用了yield方法,线程会自动释放处理器,这使得其它线程有时机获得处理器。例9.3使用yield方法重新改写前面例9.2的程序。/Li9_
27、03.javapublic class Li9_03 implements Runnable int count = 1, number; public Li9_03 (int num) number = num; System.out.println (创立线程 + number); public void run ( ) while (true) System.out.println (线程 + number + :计数 + count); if(+count = = 6) return; /run public static void main(String args) for(int
28、i = 0; i 10; i+) Li9_03 my = new Li9_03(i+1); Thread tt = new Thread(my); tt.start(); Thread.yield(); /该命令使线程自动释放处理器 9.3 线程的同步线程同步的概念 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效防止了同一个数据对象被多个线程同时访问,这套机制就是线程同步。线程同步是指Java防止多个线程同时访问一个数据而造成数据混乱的方法。它可以防止多个线程同时访问相同的数据产生线程之间的争抢,防止一
29、个线程刚生成的数据又会被其他线程生成的数据所覆盖。Java用监视器手段来完成线程的同步。就好似监视器把受保护的资源外面添加了一把锁,而这把锁只有一把钥匙。每一个线程只有在得到这把钥匙之后才可以对被保护的资源执行操作线程,而其它的线程只能等待,直到能拿到这把钥匙。Java使用关键字synchronized来实现多线程的同步,线程同步有两种实现方法,一种是方法同步,另一种是对象同步。9.3 线程的同步方法同步 一个类中任何方法都可以设计成为synchronized方法,以防止多线程数据崩溃。当一个线程进入synchronized方法后,能保证在其他任何线程访问这个方法之前完成自己的一次执行。如果一
30、个线程试图访问一个已经启动的synchronized方法,那么这个线程必须等待,直到已启动线程执行完毕,释放这个synchronized方法后才能访问。通过在方法声明中参加 synchronized关键字来声明 synchronized 方法: public synchronized void methodName(parameterList) 下面将通过模拟一个银行帐号存取款的操作来看一下线程同步的重要性。例9.4 模拟银行中的多个线程同时对同一个储蓄账户进行存款、取款操作。在主程序中我们首先生成了10个线程,然后启动它们,每一个线程都对同一账户进行存20元,然后马上又取出10元。这样,对于
31、该账户来说,最终账户的余额应该是对原帐户存款增加1000元才对。 /多线程的同步举例。Li9_04.javapublic class Li9_04 implements Runnable Account acc; public Li9_04(Account acc) this.acc = acc; public void run() acc.deposit(20.0f); acc.withdraw(10.0f); /run private static int NUM_OF_THREAD = 100; static Thread threads = new ThreadNUM_OF_THREA
32、D; /创立线程数组 public static void main(String args) final Account acc = new Account(王红, 1000.0f); for (int i = 0; i NUM_OF_THREAD; i+) Li9_04 my = new Li9_04(acc); threadsi = new Thread(my); /创立新线程 threadsi.start(); /运行线程 for (int i=0; iNUM_OF_THREAD; i+) try threadsi.join(); /等待所有线程运行结束 catch (Interrup
33、tedException e) System.out.println(完成,王红的帐户余额为: + acc.getBalance(); /main class Account String name; float amount; public Account(String name, float amount) = name; this.amount = amount; public void deposit(float amt) float tmp = amount; tmp += amt; try Thread.sleep(1);/模拟其它处理所需要的时间,比方刷新数据
34、库等 catch (InterruptedException e) amount = tmp; /deposit public void withdraw(float amt) float tmp = amount; tmp -= amt; try Thread.sleep(1);/模拟其它处理所需要的时间,比方刷新数据库等 catch (InterruptedException e) amount = tmp; /withdraw public float getBalance() return amount; /getBalance 上面在类Account的deposit和withdraw
35、方法中之所以要把对amount的运算使用一个临时变量首先存储,sleep睡眠一段时间,然后再赋值给amount,是为了模拟真实运行时的情况。因为在真实系统中,账户信息肯定是存储在持久媒介中,此处的睡眠时间相当于比较耗时的数据库操作,最后把临时变量tmp的值赋值给amount相当于把amount的改动写入数据库中。编译成功后,运行该程序某5次,结果如下:完成,王红的帐户余额为:1160.0完成,王红的帐户余额为:1040.0完成,王红的帐户余额为:1100.0完成,王红的帐户余额为:1100.0完成,王红的帐户余额为:1120.0读者在每次运行该程序时结果可能与上述结果也不相同,为什么会是这样呢
36、?这是因为多线程中的不同步问题。在例9.4中,Account类中的amount变量会同时被多个线程所访问,它是一个竞争资源,通常称作竞态条件。对于这样的多个线程共享的资源在编写程序时必须进行同步,以防止一个线程的改动被另一个线程所覆盖。amount是一个竞态条件,所有对amount的修改访问的方法都要进行同步,所以应该将deposit和withdraw两个方法进行同步,这两个方法分别修改为:public synchronized void deposit(float amt) public synchronized void withdraw(float amt)修改后重新编译并运行该程序,每
37、次运行结果都将得到王红银行最后存款余额为2000元,这样最终结果才是正确的。从本例可以看到,由于没使用synchronized关键字,线程A在对帐户操作的同时其它线程也在对帐户进行操作,而一个线程取得的帐户数据是其它线程没有提交之前的数据,所以最后线程在提交自己的数据时将会覆盖原来的数据,也就相当于只执行了其中局部线程。在使用synchronized关键字后,将会使程序中的一个线程在调用方法同步的方法时,对该方法加锁,此时其它线程将等待,直到这个线程提交数据后另外一个线程才能得到方法该方法的钥匙,即该线程能够调用该方法,所以该线程此时取得的帐户余额是上一个线程提交后的帐户余额。这样程序才能得到
38、一个正确结果,但这些都是对同步机制有所理解的前提下进行的。对象同步 synchronized关键字除了可以放在方法声明中表示整个方法为同步方法外,还可以放在对象前面限制一段代码,当某个对象用synchronized修饰时,说明该对象在任何一个时刻只能由一个线程访问。例如:synchronized(object) /允许访问控制的代码方法同步和对象同步的代码是可以相互等价转换的,例如:public synchronized void yourMethod() /修饰方法/ . 与下面对象同步的代码效果是一样的:public void yourMethod ()synchronized(this)
39、 /修饰对象的引用/ . 有时,一个方法执行时间很长,而其中只有很短的一段时间访问关键数据,在这种情况下,将整个方法声明为synchronized,将导致其他线程因无法调用该线程的synchronized方法进行操作而长时间无法继续执行,这在整体效率上是不划算的。此时,就可以使用对象同步,只把访问关键数据的代码段用花括号括起来,在其前加上synchronizedthis即可。关于对象同步在这里就不再多讲,如有兴趣的读者,可以自己把上面的银行存取款的例子使用对象同步的方法进行修改。 同步方法的缺点 同步机制虽然很方便,但可能导致死锁。死锁是指发生在线程之间相互阻塞的现象,这种现象导致同步线程相互
40、等待,以致每个线程都不能往下执行。在这种情况下,多个线程都在等待对方完成某个操作,从而产生死锁现象。例如,一个线程持有对象X,另一个线程持有对象Y。第一个线程在拥有对象X,但必须拥有第二个线程所持有的对象Y才能执行;同样,第二个线程在拥有对象Y,但必须拥有第一个线程所持有的对象X才能执行,这样这两个线程就会无限期地阻塞,这时,线程就会出现死锁。在现实程序中,错误的同步往往会出现死锁,而且是较难发现的。这就像两个人只有一双筷子使用时,每个人拿到了一根筷子,而两个人却都想得到对方的筷子,这时就产生了死锁,两人都无法得到所需的资源。为了防止死锁问题,在进行多线程程序设计时需要遵循如下原那么:在指定的
41、任务真正需要并行时才采用多线程来进行程序设计;在对象的同步方法中需要调用其他同步方法时必须小心;在synchronized封装的块中的时间尽可能的短,需要长时间运行的任务尽量不要放在synchronized封装的同步块中。另外,假设将一个大的方法声明为synchronized 将会大大影响效率。典型地,如果一个方法执行时间很长,而其中只有很短的一段时间访问关键数据,在这种情况下,将整个方法声明为synchronized,将导致其他线程因无法调用该线程的其他synchronized方法进行操作而长时间无法继续执行,这将在很大程度上降低程序的运行效率。9.4 线程组 线程组 线程组Thread g
42、roup是包括了许多线程的对象集,线程组拥有一个名字以及与它相关的一些属性,可以用于作为一个组来管理其中的线程。线程组能够有效组织JVM的线程,并且可以提供一些组间的平安性。在Java中,所有线程和线程组都隶属于一个线程组,可以是一个默认线程组,亦可是一个创立线程时明确指定的组。在创立之初,线程被限制到一个组里,而且不能改变到另外的组。假设创立多个线程而不指定一个组,它们就会自动归属于系统线程组。这样,所有线程组和线程组成了一棵以系统线程组为根的树。如图9.4所示。 图9.4 Java线程组层次例如图Thread类中提供了构造方法使创立线程时同时决定其线程组。本章第2节介绍了Thread类提供
43、六种构造方法,前四种缺省了线程组,表示所创立的线程属于main线程组,后两种那么指定了所创立线程的线程组。线程可以访问自己所在的线程组,但不能访问本线程组的父类。对线程组进行操作就是对线程组中的各个线程同时进行操作。Java的ThreadGroup类提供了一些方法来方便我们对线程组树中的线程组和线程进行操作,比方可以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程。 ThreadGroup类 Java的线程组由包java.lang中的类ThreadGroup实现。在生成线程时,可以指定将线程放在某个线程组中,也可以由系统将它放在某个缺省的线程组中。通常,缺省的
44、线程组就是生成该线程所在的线程组。但是,一旦某个线程参加某个线程组,它将一直是这个线程组的成员,而不能被移出这个线程组。在创立线程之前,可以创立一个ThreadGroup对象,下面代码创立线程组并在其中参加两个线程:ThreadGroup yourThreadGroup = new ThreadGroupx;Thread yourThread1 = new ThreadyourThreadGroup,worker1;Thread yourThread2 = new ThreadyourThreadGroup,worker2;yourThread1.start;yourThread2.start
45、;如上例所示,首先创立一个线程组,然后创立两个线程,并传递给ThreadGroup对象,称为线程组中的成员。每一个线程可以各自调用start( )方法启动。ThreadGroup类并不提供一次启动所有线程的start()方法,但是可以通过调整线程组的优先级来挂起或者运行某个线程组中的所有线程。类ThreadGroup提供了一些方法对线程组中的线程和子线程组进行操作,下面将对这些方法进行介绍。 getName():返回线程组的名字。 getParent():返回该线程的父线程组的名称。 activeCount():返回线程组中当前激活的线程的数目,包括子线程组中的活动线程。enumerate(T
46、hread ):将所有该线程组中激活的线程复制到一个特殊的数组中。setMaxPriority(int pri):设置线程组的最高优先级,pri是该线程组的新优先级,优先级从110依次增高。getMaxPriority():返回线程在线程组中拥有的最高优先级。getTheradGroup():返回线程组。interrupt():向线程组及其子组中的线程发送一个中断信息。 setDaemon(booleam daemon):将该线程设置为Daemon(守护,即常驻内存)状态。isDaemon():判断是否是Daemon线程组。isDestroyed():判断线程组是否已经被销毁。parentOf
47、(ThreadGroup g):判断线程组是否是线程组g或g的子线程组。list():对每个Thread调用toString。toString():返回一个表示本线程组的字符串,包括它的名字和最高线程优先级。另外,ThreadGroup类还提供了几个方法用来改变线程组中的所有线程的当前状态,如resume()、stop()、suspend(),但这些方法已不鼓励使用,对线程组的挂起或运行建议通过优先级的调整和让线程睡眠(sleep)来实现。例9.5 利用线程组的各种方法演示线程的根本用法。/演示线程的根本用法。Li9_05.javapublic class Li9_05public void
48、test( ) ThreadGroup tg = new ThreadGroup(test);/创立名称为test的tg线程组Thread A = new Thread(tg,A); /创立线程,并设置该线程属于tg线程组 Thread B = new Thread(tg,B); Thread C = new Thread(tg,C); A.setPriority(6); /设置线程A的优先级为6 C.setPriority(4); /设置线程C的优先级为4,线程B默认为5 System.out.println(tg线程组正在活动的线程个数:+tg.activeCount(); System.
49、out.println(线程A的优先级是:+A.getPriority(); tg.setMaxPriority(8); /设置线程组tg的优先级 System.out.println(tg线程组的优先级是:+tg.getMaxPriority(); System.out.println(tg线程组上一级线程组信息:+tg.getParent(); System.out.println(线程组名称:+tg.getName(); System.out.print(tg线程组的信息:); tg.list( ); /调用toString()方法,返回线程组的名称和优先级 /test( )public
50、 static void main(String argv)Li9_05 ttg = new Li9_05( ); /构造函数ttgttg.test( ); /运行函数ttg中的test方法 线程之间的通讯 多线程的一个重要特点是它们之间可以互相通讯,线程通信使线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程相互等待。Object类为此提供了3个函数:wait()、notify()和notifyAll()。典型的线程间通讯建立在生产者和消费者模型上:一个线程产生输出;另一个线程使用输入,先有生产者生产,才能有消费者消费。生产者未生产之前,通知消费者
51、等待;生产后通知消费者消费,消费者消费以后再通知生产者,这就是等待通知机制(wait/notify)。例9.6说明了这个模型。所谓生产者(producer)是产生一串数据流的线程,而消费者(Consumer)是消耗流中数据的线程。例如,当在键盘上敲入字符串,生产者线程把键盘事件放入事件队列,消费者线程从该队列读出事件。 例9.6 著名的生产者消费者通讯模型。本模型中两个并行线程共享同一个资源,生产者类产生09之间的整数,存入一个“Store对象,显示这个整数,还休息一段随机时间消费者类那么快速地消耗从Store刚得到的整数。Producer和Consumer共同访问Store对象,假设两者没有
52、采取同步措施,运行结果可能出现两种现象:一是当Producer比Consumer快时,生产了两个数才消费了一个;二是当Consumer比Producer快时,只生产出一个数而消费了两个数。这种现象就是竞争条件,解决的方法就是采用条件变量。本例的条件变量是Store对象,对它的关键局部用synchronized关键字标识。具体的生产者消费者问题的java算法可以用下面的代码描述。/生产者消费者通讯模型。Li9_06.java本例中Store类在get()和put()前标识了synchronized关键字,称为同步方法。拥有同步方法的Store类对象有惟一的一个监视器。当Producer调用Sto
53、re类的put()方法,生产者获得监视器,防止消费者调用Store类的get()方法,这就是锁定。只有put()方法返回,Producer释放监视器,才解锁Store对象。同理,当Consumer调用Store类的get()方法时,消费者获得监视器,防止生产者调用Store类的put()方法。这样就保证了Producer与Consumer两具线程的同步运行,即生产一个消费一个。本例中的put()方法和get()方法都采用了wait()方法和notify()方法进行同步。假设生产者没有产生数,available=false,调用get()方法的Consumer线程进入wait状态,等Produc
54、er线程放好数后令available=true,并用notify()方法唤醒Consumer取数。假设消费者没有取走数,available=true,调用put()方法的Producer线程进入wait状态,等Consumer线程取走数后令available=false,并用notify()方法唤醒Producer放数。Java是第一个在语言本身中显式地包含线程的主流编程语言,而多线程的优点是可以合理地调配多个任务,但在多个线程需要共享数据或共享存储结构时会产生执行结果的不确定性,甚至可能造成程序出现错误,所以要利用Java提供的同步机制控制互相交互的线程之间的运行进度,使线程执行时不出现错误
55、结果。huHV-boBO!5ivIV-bpCP$5ivJW+cpCP$6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0drER&7kxLY1erER*8lyLY1fsFS*8lIW+cpCP$6jwJW+cqDQ%6jwJX0dqDQ%7kxKX0drER&7kxLY1erER&8lyLY1esFS*8lyMZ2fsFS(9mzMZ2ftGT(9mzN#3gtGT)anAN#3huHU)anAO!4huHU-boBO!4ivIV-boCP$5ivIV+cpCP$5jwJW+cpDQ%6jwJW0dqDQ%6kxKX0dqER&7kxKY1erER&7lyLGT(9mAN#3gtGT)a
56、nAN#3huHU)anBO!4huHV-boBO!4ivIV-boCP$5ivIW+cpCP$6jwJW+cpDQ%6jwJX0dqDQ%7kxKX0dqER&7kxKY1erER&8lyLY1esFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzN#3gtGT(anAN#3guHU)anAO!4huHU-boBO!4hvIV-boBP$5iLY2fsFS*8mzMZ2fsGT(9mzMZ3gtGT(9nAN#3gtHU)anAN!4huHU)aoBO!4huIV-boBO$5ivIV-bpCP$5ivJW+cpCP%6jwJW+dqDQ%6jwKX0dqDQ&7kxKX0erER&7
57、kyLY1erER*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ2gtGT(9mAN#3gtGU)anAN#4huHU)anBO!4xKY1erER&8lyLY1esFS*8lyMZ2fsFS*9mzMZ2ftGT(9mzN#3gtGT)anAN#3guHU)anAO!4huHU-boBO!4hvIV-boBP$5ivIV+cpCP$5jwJW+cpCQ%6jwJW0dqDQ%6kxKX0dqER&7kxKX1erER&7lyLY1erFS*8lyLZ2fsFS*8mzMZ2fsGT(9mzM#3gtGTO!4ivIV-boCP$5ivIV+cpCP$5jwJW+cpDQ%6jwJX
58、0dqDQ%6kxKX0dqER&7kxKY1erER&8lyLY1erFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzM#3gtGT(anAN#3guHU)anAO!4huHU)boBO!4hvIV-boBP$5ivIV+cpCP$5iwJW+cpCQ%6jwJW0dqDQ%6AN#3gtGU)anAN#4huHU)aoBO!4huIV-boBO!5ivIV-bpCP$5ivJW+cpCP%6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0erER&7kxLY1erER*8lyLY1fsFS*8lzMZ2fsFS(9mzMZ2gtGT(9mAN#3gtGU)anAN#3huHU
59、)anBO!4huHV-boBO!5ivIVQ%6jxKX0dqDQ&7kxKX0erER&7kyLY1erES*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ3gtGT(9mAN#3gtGU)anAN#4huHU)aoBO!4tGU)anAN6kxKX0dqER&7kxKY1erER&8lyLY1erFS*8lyLZ2fsFS*9mzMZ2ftGT(9mzM#3gtGT(anAN#3guHU)anAO!4huHU)boBO!4hvIV-boBP$5ivIV+cpCP$5iwJW+cpCQ%6jwJW0dqDQ%6kxKX0dqDR&7kxKX1erER&7lyLY1erFS*8lyL
60、YT)anAN#3guHU)anAO!4huHU-boBO!4ivIV-boBP$5ivIV+cpCP$5jwJW+cpDQ%6jwJW0dqDQ%6kxKX0dqER&7kxKY1erER&7lyLY1erFS*8lyLZ2fsFS*9mzMZ2fsGT(9mzM#3gtGT(anAN#3guHU)anAN!4huHU)boBO!kyLY1erES*8lyLY1fsFS*8lzMZ2fsFT(9mzMZ3gtGT(9mAN#3gtGU)anAN#4huHU)aoBO!4huHV-boBO!5ivIV-bpCP$5ivJW+cpCP$6jwJW+cqDQ%6jwKX0dqDQ&7kxKX0dr
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025-2030年敏感肌友好卸妆洁面乳企业制定与实施新质生产力战略研究报告
- 2025-2030年即食红豆薏米羹行业跨境出海战略研究报告
- 2025-2030年呼吸窘迫监测设备企业制定与实施新质生产力战略研究报告
- 建筑安全施工质量监控考核试卷
- 2025-2030年古堡幽灵之夜企业制定与实施新质生产力战略研究报告
- 2025-2030年新能源汽车充电站充电行业跨境出海战略研究报告
- 2025-2030年墙板生产智能监控系统企业制定与实施新质生产力战略研究报告
- 弹簧在船舶螺旋桨动态平衡中的应用考核试卷
- 信托支持的卫星遥感应用产业考核试卷
- 二零二五年度股权代持纠纷预防及处理合同6篇
- 楼梯 栏杆 栏板(一)22J403-1
- PEP人教版小学英语六年级下册单词表(含音标)
- 多指畸形-课件
- 5G NSA站点开通指导书(临时IP开站)
- 宗教与社会课件
- 3人-机-环-管理本质安全化措施课件
- 生殖医学中心建设验收标准分析-讲座课件PPT
- 庆阳煤炭资源开发调研报告
- 桥博常见问题
- 贵州省电梯日常维护保养合同范本
- 《我们的方言》-教案(共4页)
评论
0/150
提交评论