版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Java多线程编程详解目 录 TOC o 1-3 h z u HYPERLINK l _Toc280883512 Java多线程编程详解 PAGEREF _Toc280883512 h 1 HYPERLINK l _Toc280883513 线程简介 PAGEREF _Toc280883513 h 1 HYPERLINK l _Toc280883514 一、线程概述 PAGEREF _Toc280883514 h 1 HYPERLINK l _Toc280883515 二、线程给我们带来的好处 PAGEREF _Toc280883515 h 2 HYPERLINK l _Toc28088351
2、6 三、Java的线程模型 PAGEREF _Toc280883516 h 4 HYPERLINK l _Toc280883517 用Thread类创建线程 PAGEREF _Toc280883517 h 4 HYPERLINK l _Toc280883518 用Runnable接口创建线程 PAGEREF _Toc280883518 h 8 HYPERLINK l _Toc280883519 线程的生命周期 PAGEREF _Toc280883519 h 9 HYPERLINK l _Toc280883520 一、创建并运行线程 PAGEREF _Toc280883520 h 10 HYPE
3、RLINK l _Toc280883521 二、挂起和唤醒线程 PAGEREF _Toc280883521 h 11 HYPERLINK l _Toc280883522 三、终止线程的三种方法 PAGEREF _Toc280883522 h 12 HYPERLINK l _Toc280883523 Join()方法的使用 PAGEREF _Toc280883523 h 15 HYPERLINK l _Toc280883524 慎重使用volatile关键字 PAGEREF _Toc280883524 h 17 HYPERLINK l _Toc280883525 向线程传递数据的三种方法 PAG
4、EREF _Toc280883525 h 19 HYPERLINK l _Toc280883526 一、通过构造方法传递数据 PAGEREF _Toc280883526 h 20 HYPERLINK l _Toc280883527 二、通过变量和方法传递数据 PAGEREF _Toc280883527 h 20 HYPERLINK l _Toc280883528 三、通过回调函数传递数据 PAGEREF _Toc280883528 h 21 HYPERLINK l _Toc280883529 从线程返回数据的两种方法 PAGEREF _Toc280883529 h 23 HYPERLINK l
5、 _Toc280883530 一、通过类变量和方法返回数据 PAGEREF _Toc280883530 h 23 HYPERLINK l _Toc280883531 二、通过回调函数返回数据 PAGEREF _Toc280883531 h 25 HYPERLINK l _Toc280883532 为什么要进行数据同步(加锁) PAGEREF _Toc280883532 h 25 HYPERLINK l _Toc280883533 使用synchronized关键字同步类方法 PAGEREF _Toc280883533 h 28 HYPERLINK l _Toc280883534 在使用sync
6、hronized关键字时有以下四点需要注意: PAGEREF _Toc280883534 h 32 HYPERLINK l _Toc280883535 使用synchronized的块同步 PAGEREF _Toc280883535 h 35 HYPERLINK l _Toc280883536 一、非静态类方法的同步 PAGEREF _Toc280883536 h 35 HYPERLINK l _Toc280883537 二、静态类方法的同步 PAGEREF _Toc280883537 h 37 HYPERLINK l _Toc280883538 使用synchronized块同步变量 PAG
7、EREF _Toc280883538 h 39线程简介一、线程概述 线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个主线程。 进程和线程是现代操作系统中两个必不可少的运行模型。在操作系统中可以有多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程);一个进程中可以有一个或多个线程。进程和进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间
8、中运行的。而一个进程中的线可以共享系统分派给这个进程的内存空间。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈, 是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。 注意:任何一个线程在建立时都会执行一个函数,这个函数叫做线程执行函数。也可以将这个函数看做线程的入口点(类似于程序中的main函数)。无论使用什么语言或技术来建立线程,都必须执行这个函数(这个函数的表现形式可能不一样,但都会有一个这样的函数)。如在 HYPERLINK / t _blank Windows中用于建立线程的API函数CreateThr
9、ead的第三个参数就是这个执行函数的指针。 在操作系统将进程分成多个线程后,这些线程可以在操作系统的管理下并发执行,从而大大提高了程序的运行效率。虽然线程的执行从宏观上看是多个线程同时执行,但实际上这只是操作系统的障眼法。由于一块CPU同时只能执行一条指令,因此,在拥有一块CPU的计算机上不可能同时执行两个任务。而操作系统为了能提高程序的运行效率,在一个线程空闲时会撤下这个线程,并且会让其他的线程来执行,这种方式叫做线程调度。我们之所以从表面上看是多个线程同时执行,是因为不同线程之间切换的时间非常短,而且在一般情况下切换非常频繁。假设我们有线程A和B.在运行时,可能是A执行了1毫秒后,切换到B
10、后,B又执行了1毫秒,然后又切换到了A,A又执行1毫秒。由于1毫秒的时间对于普通人来说是很难感知的,因此,从表面看上去就象A和B同时执行一样,但实际上A和B是交替执行的。二、线程给我们带来的好处 如果能合理地使用线程,将会减少开发和维护成本,甚至可以改善复杂应用程序的性能。如在GUI应用程序中,还以通过线程的异步特性来更好地处理事件;在应用 HYPERLINK / t _blank 服务器程序中可以通过建立多个线程来处理客户端的请求。线程甚至还可以简化虚拟机的实现,如 HYPERLINK / t _blank Java虚拟机(JVM)的垃圾回收器(garbage collector)通常运行在
11、一个或多个线程中。因此,使用线程将会从以下五个方面来改善我们的应用程序: 1. 充分利用CPU资源 现在世界上大多数计算机只有一块CPU.因此,充分利用CPU资源显得尤为重要。当执行单线程程序时,由于在程序发生阻塞时CPU可能会处于空闲状态。这将造成大量的计算资源的浪费。而在程序中使用多线程可以在某一个线程处于休眠或阻塞时,而CPU又恰好处于空闲状态时来运行其他的线程。这样CPU就很难有空闲的时候。因此,CPU资源就得到了充分地利用。 2.简化编程模型 如果程序只完成一项任务,那只要写一个单线程的程序,并且按着执行这个任务的步骤编写代码即可。但要完成多项任务,如果还使用单线程的话,那就得在程序
12、中判断每项任务是否应该执行以及什么时候执行。如显示一个时钟的时、分、秒三个指针。使用单线程就得在循环中逐一判断这三个指针的转动时间和角度。如果使用三个线程分另来处理这三个指针的显示,那么对于每个线程来说就是指行一个单独的任务。这样有助于开发人员对程序的理解和维护。 3.简化异步事件的处理 当一个 HYPERLINK / t _blank 服务器应用程序在接收不同的客户端连接时最简单地处理方法就是为每一个客户端连接建立一个线程。然后监听线程仍然负责监听来自客户端的请求。如果这种应用程序采用单线程来处理,当监听线程接收到一个客户端请求后,开始读取客户端发来的数据,在读完数据后,read方法处于阻塞
13、状态,也就是说,这个线程将无法再监听客户端请求了。而要想在单线程中处理多个客户端请求,就必须使用非阻塞的Socket连接和异步I/O.但使用异步I/O方式比使用同步I/O更难以控制,也更容易出错。因此,使用多线程和同步I/O可以更容易地处理类似于多请求的异步事件。 4. 使GUI更有效率 使用单线程来处理GUI事件时,必须使用循环来对随时可能发生的GUI事件进行扫描,在循环内部除了扫描GUI事件外,还得来执行其他的程序代码。如果这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。 在现代的GUI框架(如SWING、AWT和SWT)中都使用了一个单独的事件分派线程(event
14、dispatch thread,EDT)来对GUI事件进行扫描。当我们按下一个按钮时,按钮的单击事件函数会在这个事件分派线程中被调用。由于EDT的任务只是对GUI事件进行扫描,因此,这种方式对事件的反映是非常快的。 5.节约成本 提高程序的执行效率一般有三种方法: (1)增加计算机的CPU个数。 (2)为一个程序启动多个进程 (3)在程序中使用多线程。 第一种方法是最容易做到的,但同时也是最昂贵的。这种方法不需要修改程序,从理论上说,任何程序都可以使用这种方法来提高执行效率。第二种方法虽然不用购买新的硬件,但这种方式不容易共享数据,如果这个程序要完成的任务需要必须要共享数据的话,这种方式就不太
15、方便,而且启动多个进程会消耗大量的系统资源。第三种方法恰好弥补了第一种方法的缺点,而又继承了它们的优点。也就是说,既不需要购买CPU,也不会因为启太多的进程而占用大量的系统资源(在默认情况下,一个线程所占的内存空间要远比一个进程所占的内存空间小得多),并且多线程可以模拟多块CPU的运行方式,因此,使用多线程是提高程序执行效率的最廉价的方式。三、 HYPERLINK / t _blank Java的线程模型 由于Java是纯面向对象语言,因此,Java的线程模型也是面向对象的。Java通过Thread类将线程所必须的功能都封装了起来。要想建立一个线程,必须要有一个线程执行函数,这个线程执行函数对
16、应Thread类的run方法。Thread类还有一个start方法,这个方法负责建立线程,相当于调用 HYPERLINK / t _blank Windows的建立线程函数CreateThread.当调用start方法后,如果线程建立成功,并自动调用Thread类的run方法。因此,任何继承Thread的Java类都可以通过Thread类的start方法来建立线程。如果想运行自己的线程执行函数,那就要覆盖Thread类的run方法。 在Java的线程模型中除了Thread类,还有一个标识某个Java类是否可作为线程类的接口Runnable,这个接口只有一个抽象方法run,也就是Java线程模型
17、的线程执行函数。因此,一个线程类的唯一标准就是这个类是否实现了Runnable接口的run方法,也就是说,拥有线程执行函数的类就是线程类。从上面可以看出,在Java中建立线程有两种方法,一种是继承Thread类,另一种是实现Runnable接口,并通过Thread和实现Runnable的类来建立线程,其实这两种方法从本质上说是一种方法,即都是通过Thread类来建立线程,并运行run方法的。但它们的大区别是通过继承Thread类来建立线程,虽然在实现起来更容易,但由于Java不支持多继承,因此,这个线程类如果继承了Thread,就不能再继承其他的类了,因此,Java线程模型提供了通过实现Run
18、nable接口的方法来建立线程,这样线程类可以在必要的时候继承和业务有关的类,而不是Thread类。用Thread类创建线程在 HYPERLINK / t _blank Java中创建线程有两种方法:使用Thread类和使用Runnable接口。在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。Thread类的构造方法被重载了八次,构造方法如下:publicThread();publicThread(Runnabletarget);publicThread(Stringname);
19、publicThread(Runnabletarget,Stringname);publicThread(ThreadGroupgroup,Runnabletarget);publicThread(ThreadGroupgroup,Stringname);publicThread(ThreadGroupgroup,Runnabletarget,Stringname);publicThread(ThreadGroupgroup,Runnabletarget,Stringname,longstackSize); Runnable target 实现了Runnable接口的类的实例。要注意的是Thr
20、ead类也实现了Runnable接口,因此,从Thread类继承的类的实例也可以作为target传入这个构造方法。 String name 线程的名子。这个名子可以在建立Thread实例后通过Thread类的setName方法设置。如果不设置线程的名子,线程就使用默认的线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数。 ThreadGroup group 当前建立的线程所属的线程组。如果不指定线程组,所有的线程都被加到一个默认的线程组中。关于线程组的细节将在后面的章节详细讨论。 long stackSize 线程栈的大小,这个值一般是CPU页面的整数倍。如x86的页面大小是4
21、KB.在x86平台下,默认的线程栈大小是12KB. 一个普通的 HYPERLINK / t _blank Java类只要从Thread类继承,就可以成为一个线程类。并可通过Thread类的start方法来执行线程代码。虽然Thread类的子类可以直接实例化,但在子类中必须要覆盖Thread类的run方法才能真正运行线程的代码。下面的代码给出了一个使用Thread类建立线程的例子:001packagemythread;002003publicclassThread1extendsThread004005publicvoidrun()006007System.out.println(this.ge
22、tName();008009publicstaticvoidmain(Stringargs)010011System.out.println(Thread.currentThread().getName();012Thread1thread1=newThread1();013Thread1thread2=newThread1();014thread1.start();015thread2.start();016017 上面的代码建立了两个线程:thread1和thread2.上述代码中的005至008行是Thread1类的run方法。当在014和015行调用start方法时,系统会自动调用ru
23、n方法。在007行使用this.getName()输出了当前线程的名字,由于在建立线程时并未指定线程名,因此,所输出的线程名是系统的默认值,也就是Thread-n的形式。在011行输出了主线程的线程名。 上面代码的运行结果如下:mainThread-0Thread-1 从上面的输出结果可以看出,第一行输出的main是主线程的名子。后面的Thread-1和Thread-2分别是thread1和thread2的输出结果。 注意:任何一个Java程序都必须有一个主线程。一般这个主线程的名子为main.只有在程序中建立另外的线程,才能算是真正的多线程程序。也就是说,多线程程序必须拥有一个以上的线程。T
24、hread类有一个重载构造方法可以设置线程名。除了使用构造方法在建立线程时设置线程名,还可以使用Thread类的setName方法修改线程名。要想通过Thread类的构造方法来设置线程名,必须在Thread的子类中使用Thread类的public Thread(String name)构造方法,因此,必须在Thread的子类中也添加一个用于传入线程名的构造方法。下面的代码给出了一个设置线程名的例子:001packagemythread;002003publicclassThread2extendsThread004005privateStringwho;006007publicvoidrun(
25、)008009System.out.println(who+:+this.getName();010011publicThread2(Stringwho)012013super();014this.who=who;015016publicThread2(Stringwho,Stringname)017018super(name);019this.who=who;020021publicstaticvoidmain(Stringargs)022023Thread2thread1=newThread2(thread1,MyThread1);024Thread2thread2=newThread2(
26、thread2);025Thread2thread3=newThread2(thread3);026thread2.setName(MyThread2);027thread1.start();028thread2.start();029thread3.start();030031 在类中有两个构造方法: 第011行:public sample2_2(String who) 这个构造方法有一个参数:who.这个参数用来标识当前建立的线程。在这个构造方法中仍然调用Thread的默认构造方法public Thread()。 第016行:public sample2_2(String who, Str
27、ing name) 这个构造方法中的who和第一个构造方法的who的含义一样,而name参数就是线程的名名。在这个构造方法中调用了Thread类的public Thread(String name)构造方法,也就是第018行的super(name)。 在main方法中建立了三个线程:thread1、thread2和thread3.其中thread1通过构造方法来设置线程名,thread2通过setName方法来修改线程名,thread3未设置线程名。 运行结果如下:thread1:MyThread1thread2:MyThread2thread3:Thread-2 从上面的输出结果可以看出,t
28、hread1和thread2的线程名都已经修改了,而thread3的线程名仍然为默认值:Thread-2.thread3的线程名之所以不是Thread-1,而是Thread-2,这是因为在024行建立thread2时已经将Thread-1占用了,因此,在025行建立thread3时就将thread3的线程名设为Thread-2.然后在026行又将thread2的线程名修改为MyThread2.因此就会得到上面的输出结果。 注意:在调用start方法前后都可以使用setName设置线程名,但在调用start方法后使用setName修改线程名,会产生不确定性,也就是说可能在run方法执行完后才会执
29、行setName.如果在run方法中要使用线程名,就会出现虽然调用了setName方法,但线程名却未修改的现象。Thread类的start方法不能多次调用,如不能调用两次thread1.start()方法。否则会抛出一个IllegalThreadStateException异常。用Runnable接口创建线程实现Runnable接口的类必须使用Thread类的实例才能创建线程。通过Runnable接口创建线程分为两步: 1. 将实现Runnable接口的类实例化。 2.建立一个Thread对象,并将第一步实例化后的对象作为参数传入Thread类的构造方法。 最后通过Thread类的start方
30、法建立线程。 下面的代码演示了如何使用Runnable接口来创建线程:packagemythread;publicclassMyRunnableimplementsRunnablepublicvoidrun()System.out.println(Thread.currentThread().getName();publicstaticvoidmain(Stringargs)MyRunnablet1=newMyRunnable();MyRunnablet2=newMyRunnable();Threadthread1=newThread(t1,MyThread1);Threadthread2=n
31、ewThread(t2);thread2.setName(MyThread2);thread1.start();thread2.start(); 上面代码的运行结果如下:MyThread1MyThread2线程的生命周期与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中和这四种状态相关的方法。/开始线程 publicvoidstart(); publicvoidrun();/挂起和唤醒线程 publicvoidresume();/不建议使用 publicvoidsuspend();/不
32、建议使用publicstaticvoidsleep(longmillis); publicstaticvoidsleep(longmillis,intnanos); /终止线程 publicvoidstop();/不建议使用publicvoidinterrupt(); /得到线程状态publicbooleanisAlive();publicbooleanisInterrupted(); publicstaticbooleaninterrupted(); /join方法publicvoidjoin()throwsInterruptedException;一、创建并运行线程 线程在建立后并不马上执
33、行run方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程的各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。 当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。下面的代码演示了线程的创建、运行和停止三个状态之间的切换,并输出了相应的isAlive返回值。packagecha
34、pter2;publicclassLifeCycleextendsThreadpublicvoidrun()intn=0;while(+n)1000);publicstaticvoidmain(Stringargs)throwsExceptionLifeCyclethread1=newLifeCycle();System.out.println(isAlive:+thread1.isAlive();thread1.start();System.out.println(isAlive:+thread1.isAlive();thread1.join();/等线程thread1结束后再继续执行Sys
35、tem.out.println(thread1已经结束!);System.out.println(isAlive:+thread1.isAlive(); 要注意一下,在上面的代码中使用了join方法,这个方法的主要功能是保证线程的run方法完成后程序才继续运行,这个方法将在后面的文章中介绍 上面代码的运行结果:isAlive:falseisAlive:truethread1已经结束!isAlive:false二、挂起和唤醒线程 一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sle
36、ep.在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。 虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。下面的代码演示了sleep、suspend和resume三个方法的使用。packagechapter2;public
37、classMyThreadextendsThreadclassSleepThreadextendsThreadpublicvoidrun()trysleep(2000);catch(Exceptione)publicvoidrun()while(true)System.out.println(newjava.util.Date().getTime();publicstaticvoidmain(Stringargs)throwsExceptionMyThreadthread=newMyThread();SleepThreadsleepThread=thread.newSleepThread();
38、sleepThread.start();/开始运行线程sleepThreadsleepThread.join();/使线程sleepThread延迟2秒thread.start();booleanflag=false;while(true)sleep(5000);/使主线程延迟5秒flag=!flag;if(flag)thread.suspend();elsethread.resume(); 从表面上看,使用sleep和suspend所产生的效果类似,但sleep方法并不等同于suspend.它们之间最大的一个区别是可以在一个线程中通过suspend方法来挂起另外一个线程,如上面代码中在主线程
39、中挂起了thread线程。而sleep只对当前正在执行的线程起作用。在上面代码中分别使sleepThread和主线程休眠了2秒和5秒。在使用sleep时要注意,不能在一个线程中来休眠另一个线程。如main方法中使用thread.sleep(2000)方法是无法使thread线程休眠2秒的,而只能使主线程休眠2秒。 在使用sleep方法时有两点需要注意: 1. sleep方法有两个重载形式,其中一个重载形式不仅可以设毫秒,而且还可以设纳秒(1,000,000纳秒等于1毫秒)。但大多数操作系统平台上的 HYPERLINK / t _blank Java虚拟机都无法精确到纳秒,因此,如果对sleep
40、设置了纳秒, HYPERLINK / t _blank Java虚拟机将取最接近这个值的毫秒。 2. 在使用sleep方法时必须使用throws或trycatch.因为run方法无法使用throws,所以只能使用trycatch.当在线程休眠的过程中,使用interrupt方法(这个方法将在2.3.3中讨论)中断线程时sleep会抛出一个InterruptedException异常。sleep方法的定义如下:publicstaticvoidsleep(longmillis)throwsInterruptedExceptionpublicstaticvoidsleep(longmillis,in
41、tnanos)throwsInterruptedException三、终止线程的三种方法 有三种方法可以使终止线程。 1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。 3. 使用interrupt方法中断线程。 1. 使用退出标志终止线程 当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循
42、环。如果想让循环永远运行下去,可以使用while(true)来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。下面给出了一个利用退出标志终止线程的例子。packagechapter2;publicclassThreadFlagextendsThreadpublicvolatilebooleanexit=false;publicvoidrun()while(!exit);publicstaticvoidmain(Stringargs)throwsExceptionThreadF
43、lagthread=newThreadFlag();thread.start();sleep(5000);/主线程延迟5秒thread.exit=true;/终止线程threadthread.join();System.out.println(线程退出!); 在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值, 2. 使用stop方法终止线程 使用stop方法可以强行终止正在运行或挂起的线
44、程。我们可以使用如下的代码来终止线程:thread.stop(); 虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。 3. 使用interrupt方法终止线程 使用interrupt方法来终端线程可分为两种情况: (1)线程处于阻塞状态,如使用了sleep方法。 (2)使用while(!isInterrupted()来判断线程是否被中断。 在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况
45、下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。packagechapter2;publicclassThreadInterruptextendsThreadpublicvoidrun()trysleep(50000);/延迟50秒catch(InterruptedExceptione)System.out.println(e.getMessage();publicstaticvoidmain(Stringargs)throwsExceptionThreadthread=newThreadInterrupt();thread.start();System.out.p
46、rintln(在50秒之内按任意键中断线程!);System.in.read();errupt();thread.join();System.out.println(线程已经退出!); 上面代码的运行结果如下:在50秒之内按任意键中断线程! sleepinterrupted 线程已经退出! 在调用interrupt方法后, sleep方法抛出异常,然后输出错误信息:sleep interrupted. 注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别
47、是interrupted用来判断当前线程是否被中断,而isInterrupted可以用来判断其他线程是否被中断。因此,while (!isInterrupted()也可以换成while(!Terrupted()。Join()方法的使用在上面的例子中多次使用到了Thread类的join方法。我想大家可能已经猜出来join方法的功能是什么了。对,join方法的功能就是使异步执行的线程变成同步执行。也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用join方法。如果不使用join方法,就不能保证当执行到start
48、方法后面的某条语句时,这个线程一定会执行完。而使用join方法后,直到这个线程退出,程序才会往下执行。下面的代码演示了join的用法。package mythread;publicclassJoinThreadextendsThreadpublicstaticvolatileintn=0;publicvoidrun()for(inti=0;i10;i+,n+)trysleep(3);/为了使运行结果更随机,延迟3毫秒catch(Exceptione)publicstaticvoidmain(Stringargs)throwsExceptionThreadthreads=newThread100
49、;for(inti=0;ithreads.length;i+)/建立100个线程threadsi=newJoinThread();for(inti=0;i0)for(inti=0;ithreads.length;i+)/100个线程都执行完后继续threadsi.join();System.out.println(n=+JoinThread.n); 在例程2-8中建立了100个线程,每个线程使静态变量n增加10.如果在这100个线程都执行完后输出n,这个n值应该是1000. 1. HYPERLINK t _blank 测试1 使用如下的命令运行上面程序:java mythread.JoinTh
50、read 程序的运行结果如下:n=442 这个运行结果可能在不同的运行环境下有一些差异,但一般n不会等于1000.从上面的结果可以肯定,这100个线程并未都执行完就将n输出了。 2. HYPERLINK t _blank 测试2使用如下的命令运行上面的代码:java mythread.JoinThread join 在上面的命令行中有一个参数join,其实在命令行中可以使用任何参数,只要有一个参数就可以,这里使用join,只是为了表明要使用join方法使这100个线程同步执行。 程序的运行结果如下:n=1000 无论在什么样的运行环境下运行上面的命令,都会得到相同的结果:n=1000.这充分说
51、明了这100个线程肯定是都执行完了,因此,n一定会等于1000.慎重使用volatile关键字volatile关键字相信了解 HYPERLINK / t _blank Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:packagemythread;publicclassJoinThreadextendsThreadpublicstatic volatile intn=0; publ
52、icvoidrun()for(inti=0;i10;i+)tryn=n+1;sleep(3);/为了使运行结果更随机,延迟3毫秒catch(Exceptione)publicstaticvoidmain(Stringargs)throwsExceptionThreadthreads=newThread100;for(inti=0;ithreads.length;i+)/建立100个线程threadsi=newJoinThread();for(inti=0;ithreads.length;i+)/运行刚才建立的100个线程threadsi.start();for(inti=0;ithreads.
53、length;i+)/100个线程都执行完后继续threadsi.join();System.out.println(n=+JoinThread.n); 如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:n=n+1;n+; 如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:packagemythread
54、;publicclassJoinThreadextendsThreadpublicstatic intn=0;public staticsynchronizedvoidinc()n+;publicvoidrun()for(inti=0;i10;i+)tryinc();/n=n+1改成了inc();sleep(3);/为了使运行结果更随机,延迟3毫秒catch(Exceptione)publicstaticvoidmain(Stringargs)throwsExceptionThreadthreads=newThread100;for(inti=0;ithreads.length;i+)/建立1
55、00个线程threadsi=newJoinThread();for(inti=0;ithreads.length;i+)/运行刚才建立的100个线程threadsi.start();for(inti=0;ithreads.length;i+)/100个线程都执行完后继续threadsi.join();System.out.println(n=+JoinThread.n); 上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原子
56、操作,当变量的值由自身的上一个决定时,如n=n+1、n+等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原子级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile.向线程传递数据的三种方法在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数
57、一样通过函数参数和return语句来返回数据。本文就以上原因介绍了几种用于向线程传递数据的方法,在下一篇文章中将介绍从线程中返回数据的方法。 欲先取之,必先予之。一般在使用线程时都需要有一些初始化数据,然后线程利用这些数据进行加工处理,并返回结果。在这个过程中最先要做的就是向线程中传递数据。一、通过构造方法传递数据 在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:package myt
58、hread;publicclassMyThread1extendsThreadprivateStringname;publicMyThread1(Stringname)=name;publicvoidrun()System.out.println(hello+name);publicstaticvoidmain(Stringargs)Threadthread=newMyThread1(world);thread.start(); 由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用
59、集合、类等数据结构。使用构造方法来传递数据虽然比较 HYPERLINK / t _blank 安全,但如果要传递的数据比较多时,就会造成很多不便。由于 HYPERLINK / t _blank Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。二、通过变量和方法传递数据 向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋
60、值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:packagemythread;publicclassMyThread2implementsRunnableprivateStringname;publicvoidsetName(Stringname)=name;publicvoidrun()System.out.println(hello+name);publicstaticvoidmain(Stringargs)MyThread2myThread=newMyThread2();myThread.setName(world);Threadthrea
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- GBT 13025.1-2012制盐工业通 用试验方法 粒度的测定专题研究报告
- 《GB-T 39436-2020病媒生物防制操作规程 地铁》专题研究报告
- 《GB-T 10987-2009光学系统 参数的测定》专题研究报告
- 云安全运维服务协议
- 智能家居行业智能家电测试工程师岗位招聘考试试卷及答案
- 2025年10月12日芜湖弋江社区工作者面试真题及答案解析
- 2025年柔性制造单元(FMC)合作协议书
- 肠道菌群与免疫健康的关系
- 2025年食品冷冻机械项目发展计划
- 风湿病的康复指导
- 2025四川航天川南火工技术有限公司招聘考试题库及答案1套
- 2025年度皮肤科工作总结及2026年工作计划
- 冠状动脉微血管疾病诊断和治疗中国专家共识(2023版)
- 2024年春季学期中国文学基础#期末综合试卷-国开(XJ)-参考资料
- 军队物资工程服务采购产品分类目录
- 广西柳州市2023-2024学年八年级上学期期末质量监测地理试卷
- 《天文教学设计》教学设计
- 大学通用俄语1
- GB/T 24002.1-2023环境管理体系针对环境主题领域应用GB/T 24001管理环境因素和应对环境状况的指南第1部分:通则
- GB/T 16938-2008紧固件螺栓、螺钉、螺柱和螺母通用技术条件
- C语言课程设计-商品信息管理系统
评论
0/150
提交评论