第7章 Java多线程Word格式文档下载.docx
《第7章 Java多线程Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《第7章 Java多线程Word格式文档下载.docx(31页珍藏版)》请在冰点文库上搜索。
(1)扩展Thread类。
(2)实现Runnable接口。
7.3.1扩展Thread类
要创建线程,首先定义一个Thread类的子类,在该类中重写thread类的run()方法,即定义线程所需完成的工作。
Thread类常用的构造方法如下:
publicThread():
创建新的Thread对象;
publicThread(Stringname):
分配名字为name的新thread对象。
Thread类常用的方法如下:
publicstaticvoidsleep(longmillis)throwsinterruptedException:
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
publicvoidstart():
使该线程开始执行;
Java虚拟机调用该线程的run()方法。
publicvoidrun():
定义该线程需执行的任务。
publicfinalStringgetName():
返回该线程的名称。
publicfinalBooleanisAlive():
测试线程是否处于活动状态。
如果线程已经启动且尚未终止,则为活动状态。
publicstaticThreadcurrentThread():
返回当前正在执行的线程对象的引用。
【例7-1】通过扩展thread类的方法创建线程示例。
1publicclassTestMitiThread{
2publicstaticvoidmain(String[]args){
3System.out.println(Thread.currentThread().getName()+"
线程运行开始!
"
);
4newMitiSay("
A"
).start();
5newMitiSay("
B"
6System.out.println(Thread.currentThread().getName()+"
线程运行结束!
7}
8}
9classMitiSayextendsThread{
10publicMitiSay(StringthreadName){
11super(threadName);
12}
13publicvoidrun(){
14System.out.println(getName()+"
15for(inti=0;
i<
10;
i++){
16System.out.println(i+"
"
+getName());
17try{
18sleep((int)Math.random()*1000);
19}catch(InterruptedExceptione){
20e.printStackTrace();
21}
22}
23System.out.println(getName()+"
24}
25}
程序运行结果:
D:
\java\7>
javacExample7_1.java
javaExample7_1
main线程运行开始!
A线程运行开始!
0A
main线程运行结束!
B线程运行开始!
1A
0B
2A
1B
3A
2B
4A
5A
4B
6A
5B
7A
6B
8A
7B
9A
8B
A线程运行结束!
9B
B线程运行结束!
程序说明:
(1)程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。
随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
(2)在一个方法中调用Thread.currentThread().getName()方法,可以获取当前线程的名字。
在mian方法中调用该方法,获取的是主线程的名字。
(3)start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。
因此,只有乱序执行的代码才有必要设计为多线程。
(4)Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
7.3.2实现runnable接口
上一节介绍了通过继承thread类创建线程的方法,现在再来介绍另一种创建线程的方法:
实现runnable接口。
在面向对象一章中,我们已经知道,在Java语言中只支持单继承,当所定义的线程类已经是某个类的子类,此时继承thread类创建线程的方法就行不通了,必须采用这一节的方法:
实现runnable接口创建线程。
实现runnable接口需要引用thread类的另一种构造方法:
PublicThread(Runnabletarget):
分配新的Thread对象。
【例7-2】通过实现runnable接口的方法创建线程。
1publicclassTestMitiThread1implementsRunnable{
2publicstaticvoidmain(String[]args){
3System.out.println(Thread.currentThread().getName()+"
4TestMitiThread1test=newTestMitiThread1();
5Threadthread1=newThread(test);
6Threadthread2=newThread(test);
7thread1.start();
8thread2.start();
9System.out.println(Thread.currentThread().getName()+"
10}
11publicvoidrun(){
12System.out.println(Thread.currentThread().getName()+"
13for(inti=0;
14System.out.println(i+"
+Thread.currentThread().getName());
15try{
16Thread.sleep((int)Math.random()*10);
17}catch(InterruptedExceptione){
18e.printStackTrace();
19}
20}
21System.out.println(Thread.currentThread().getName()+"
23}
Thread-0线程运行开始!
0Thread-0
Thread-1线程运行开始!
0Thread-1
1Thread-1
1Thread-0
2Thread-0
2Thread-1
3Thread-0
3Thread-1
4Thread-0
4Thread-1
5Thread-0
6Thread-0
5Thread-1
7Thread-0
8Thread-0
6Thread-1
9Thread-0
7Thread-1
Thread-0线程运行结束!
8Thread-1
9Thread-1
Thread-1线程运行结束!
(1)TestMitiThread1类通过实现Runnable接口,使得该类有了多线程类的特征。
run()方法是多线程程序的一个约定。
所有的多线程代码都在run方法里面。
Thread类实际上也是实现了Runnable接口的类。
(2)在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnabletarget)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
(3)实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。
因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
7.3.3两种创建线程方法的比较
(1)扩展thread类
该方法较简单,采用了Java面向对象的继承机制,但有碍于Java语言只支持单继承的特点,使用比较单一,有很大的局限性。
在继承了thread后,不能再继承其他类。
(2)实现runnable接口
该方法采用实现接口的方式,具有很好的灵活型。
既避免了Java单继承性带来的局限,又适合多个相同程序代码的线程去处理统一资源的情况,实现了资源共享。
【例7-3】Thread类创建线程模拟铁路售票系统的差异演示。
说明:
该系统有四个售票点,发售某日某次列车的10张车票。
要求:
一个售票点用一个线程表示。
1publicclassExample7_3{
2publicstaticvoidmain(String[]agrs){
3newThreadTest().start();
4newThreadTest().start();
5newThreadTest().start();
6newThreadTest().start();
7}
8}
9classThreadTestextendsThread{
10privateintticket=10;
11publicvoidrun(){
12while(true){
13if(ticket>
0){
14System.out.println(Thread.currentThread().getName()+"
isselling15ticket"
+ticket--);
15}
16else{
17break;
18}
19}
20}
21}
javacExample7_3.java
javaExample7_3
Thread-0issellingticket10
Thread-0issellingticket9
Thread-1issellingticket10
Thread-0issellingticket8
Thread-0issellingticket7
Thread-2issellingticket10
Thread-3issellingticket10
Thread-2issellingticket9
Thread-0issellingticket6
Thread-1issellingticket9
Thread-0issellingticket5
Thread-2issellingticket8
Thread-3issellingticket9
Thread-2issellingticket7
Thread-0issellingticket4
Thread-1issellingticket8
Thread-0issellingticket3
Thread-2issellingticket6
Thread-3issellingticket8
Thread-2issellingticket5
Thread-2issellingticket4
Thread-2issellingticket3
Thread-2issellingticket2
Thread-2issellingticket1
Thread-0issellingticket2
Thread-1issellingticket7
Thread-0issellingticket1
Thread-3issellingticket7
Thread-1issellingticket6
Thread-1issellingticket5
Thread-1issellingticket4
从结果上看每个票号都被打印了四次,即四个线程各自卖各自的10张票,而不去卖共同的10张票。
这种情况是怎么造成的呢?
我们需要的是,多个线程去处理同一个资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个ThreadTest对象,就等于创建了四个资源,每个资源都有10张票,每个线程都在独自处理各自的资源。
经过这些实验和分析,可以总结出,要实现这个铁路售票程序,我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,并且每个线程上所运行的是相同的程序代码。
【例7-4】利用接口创建线程模拟铁路售票系统的差异演示。
1publicclassThreadDemo2{
2publicstaticvoidmain(String[]args){
3ThreadTestt=newThreadTest();
4newThread(t).start();
5newThread(t).start();
6newThread(t).start();
7newThread(t).start();
8}
9}
10classThreadTestimplementsRunnable{
11privateinttickets=10;
12publicvoidrun(){
13while(true){
14if(tickets>
0){
15System.out.println(Thread.currentThread().getName()+
16"
issalingticket"
+tickets--);
17}
18}
19}
20}
javacExample7_4.java
javaExample7_4
Thread-3issellingticket6
Thread-1issellingticket2
上面的程序中,创建了四个线程,每个线程调用的是同一个ThreadTest对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。
在Windows上可以启动多个记事本程序一样,也就是多个进程使用同一个记事本程序代码。
7.4线程的生命周期及调度
在java中每个线程都要经历五种状态:
新建,就绪,运行,阻塞和死亡。
线程从新生到死亡的状态变化过程称为生命周期。
下面结合图7-1来说明线程的生命周期。
执行完毕
解除阻塞
导致阻塞的事件
start()
调度
图7-1线程的生命周期
新建状态:
使用new创建线程对象,但此时线程并未执行。
就绪状态:
当线程对象调用了start方法以后,线程就处于就绪状态,JAVA虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有运行,只是表示可以运行而已。
启动线程使用start方法,而不是run方法,调用start方法来启动线程,系统会把该run方法当成线程执行体来处理,但如果直接调用线程对象的run方法,则run方法会被立即执行,而且在run方法之前的其他线程无法并行执行。
运行状态:
当线程处于就绪状态获得CPU,就开始执行run方法,程序进入运行状态。
一条线程开始运行以后,不可能一直处于运行状态,线程运行状态过程中也会被中断,目的是给其他线程获得执行的机会。
系统会给每个可执行的线程一小段时间来处理任务。
阻塞状态:
当发生如下情况,线程会进入阻塞状态:
1.线程调用sleep方法主动放弃所占用的资源。
2.线程调用了一个阻塞式IO方法,在该方法返回以前,该线程阻塞。
3.线程试图获得一个同步监视器,但该同步监视器正被别的线程所持有。
4.线程在等待某个通知。
5.线程调用了suspend方法将该线程挂起。
终止状态:
1.线程的run方法执行完成,线程正常结束。
2.线程抛出一个未捕获的exception或error。
3.直接调用线程的stop方法来结束该线程。
为了测试某线程是否已经死亡,可以调用线程的isAlive()方法,当线程处于就绪,运行,阻塞的时候返回true,当线程处于新建和死亡状态,该方法返回false。
当对已经死亡的线程调用start方法,会抛出illegalThreadStateException异常。
7.5线程的终止
有三种方法可以终止线程。
1.使用退出标志终止线程
当run方法执行完后,线程就会退出,但有时run方法时永远不会结束的,如在服务器程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。
在这种情况下,一般是将这些任务放在一个循环中,如while循环。
想使while循环在某一特定条件下退出,最直接的方法就是设计一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。
【例7-5】线程终止示例。
1publicclassExample7_5extendsThread{
2publicbooleanexit=false;
3publicvoidrun(){
4while(!
exit);
5}
6publicstaticvoidmain(String[]args)throwsException{
7Example7_5thread=newExample7_5();
8sleep(5000);
9thread.join();
10System.out.println("
线程退出!
11}
12}
javacExample7_5.java
javaExample7_5
程序分析:
在上面代码中定义了一个退出exit,当exit为true时,while循环退出,exit的默认值为false。
2.使用stop方法终止线程
使用stop方法可以强行终止正在运行或挂起的线程。
我们可以使用如下的代码来终止线程:
Thread.stop();
虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就像突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。
3.使用interrupt方法终止线程
使用interrupt方法来终止线程可分为两种情况:
(1)线程处于阻塞状态,如使用了sleep方法
(2)使用while(!
isInterrupted()){…}来判断线程是否被中断。
在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException异常,而在第二种情况下线程将直接退出
7.6线程同步
7.6.1线程同步问题
线程同步是为了防止多个线程同时访问一个数据对象时,对数据造成的破坏。
例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件