第5章多线程操作Word下载.docx
- 文档编号:20942105
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:45
- 大小:314.14KB
第5章多线程操作Word下载.docx
《第5章多线程操作Word下载.docx》由会员分享,可在线阅读,更多相关《第5章多线程操作Word下载.docx(45页珍藏版)》请在冰豆网上搜索。
ThreadDemo1.java
publicclassThreadDemo1
{
publicstaticvoidmain(Stringargs[])
{
newTestThread().run();
while(true)
{
System.out.println("
mainthreadisrunning"
);
}
}
}
classTestThread
publicvoidrun()
System.out.println(Thread.currentThread().getName()+
"
isrunning"
}
一个代码段被执行,一定是在某个线程上运行的,代码与线程密不可分,同一段代码可以与多个线程相关联,在多个线程上执行的也可以是相同的一段代码,好比多个火车售票处按相同的操作流程(相当程序代码)同时售票一样。
在上面的代码中,我们使用Thread.currentThread()静态函数获得该代码当前执行时对应的那个线程对象。
得到当前线程对象后,我们又调用了线程对象的getName()方法,取出当前线程的名称字符串。
代码块1处的代码能否运行呢?
编译ThreadDemo1.java文件,并运行一下,看看结果如何?
图5.1
屏幕上不停地打印出mainisrunning,而不是mainthreadisrunning,这说明代码块1处的程序没有运行,因为代码块2先于代码块1运行,且代码块2为无限循环,代码块1永远没有机会运行。
同时,我们也能够看到当前线程的名称为main。
我们将代码进行如下修改(为了达到对比讲解,保持上下连贯性的效果,我们对修改过的地方进行注释,而不是彻底删除掉):
ThreadDemo2.java
publicclassThreadDemo2
newTestThread().start();
/*run()*/
classTestThreadextendsThread
System.out.println(Thread.currentThread().getName()+
isrunning"
上面的代码让TestThread类继承了Thread类,也就是TestThread类具有了Thread类的全部特点,程序没有直接调用TestThread类对象的run方法,而是调用了该类对象从Thread类继承来的start方法。
运行一下,我们能够看到两个while循环处的代码同时交替运行:
图5.2
这就是我们要讲的多线程。
单线程与多线程的区别,如图5.3所示。
图5.3
可见,在单线程中,main函数必须等到TestThread.run函数返回后才能继续往下执行。
而在多线程中,main函数调用TestThread.start()方法启动了TestThread.run()函数后,main函数不等待TestThread.run函数返回就继续运行,TestThread.run函数在一边独自运行,不影响原来的main函数的运行,这好比将一个1G的CPU分成了两个500M的CPU,在一个CPU上运行main函数,而TestThread.run则在另一个CPU上运行,但它们都在向同一个显示器上输出,所以我们看到两个while循环处的代码同时交替运行。
同样,在代码段run()中,我们也可以通过线程的静态方法Thread.currentThread()得到当前线程实例对象。
得到当前线程对象后,我们又调用了线程对象的getName()方法,取出当前线程的名称字符串
小结:
1.要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread类的子类。
倒过来看,我们要实现多线程,必须编写一个继承了Thread类的子类,子类要覆盖Thread类中的run函数,在子类的run函数中调用想在新线程上运行的程序代码。
2.启动一个新的线程,我们不是直接调用Thread的子类对象的run方法,而是调用Thread子类对象的start(从Thread类中继承的)方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法,根据面向对象的多态性,在该线程上实际运行的是Thread子类(也就是我们编写的那个类)对象中的run方法。
3.由于线程的代码段在run方法中,那么该方法执行完成以后线程也就相应的结束了,因而我们可以通过控制run方法中的循环条件来控制线程的终止。
查看JDK文档中Thread类,发现有许多构造方法,在我们上面的例子中,线程对象都是通过Thread()构造方法创建的,线程将调用线程对象中的run()方法作为其运行代码,具体细节大家已在前面看到了,请大家思考一个问题:
如果Thread类的子类(在上面的例子中即TestThread类)没有覆盖run方法,编译和运行时有明显的错误或异常吗?
运行结果是怎样的呢?
请读者自己做试验来证明一下。
提示:
程序会调用Thread类中的run方法,而该run方法什么也不做,所以,新的线程刚一产生就结束了,这样创建出来的线程对我们的程序来说毫无意义。
直接在程序中写newThread().start();
这样的语句,编译和运行时有明显的错误或异常吗?
由于我们的线程对象不是通过Thread子类创建的,而是通过Thread类直接创建的,新的线程将直接调用Thread类中的run()方法,所以答案与上面的一样。
使用Thread()构造方法,适用于覆盖了run方法的Thread子类创建线程对象的情况,如我们前面的例子那样。
5.1.3使用Runnable接口创建多线程
在JDK文档中,我们还看到了一个Thread(Runnabletarget)构造方法,从JDK文档中查看Runnable接口类的帮助,该接口中只有一个run()方法,当我们使用Thread(Runnabletarget)方法创建线程对象时,需为该方法传递一个实现了Runnable接口的类对象,这样创建的线程将调用那个实现了Runnable接口的类对象中的run()方法作为其运行代码,而不再调用Thread类中的run方法了。
我们可以将上面的例子改写成下面这样:
ThreadDemo3.java
publicclassThreadDemo3
publicstaticvoidmain(Stringargs[])
//newTestThread().start();
TestThreadtt=newTestThread();
//创建TestThread类的一个实例
Threadt=newThread(tt);
//创建一个Thread类的实例
t.start();
//使线程进入Runnable状态
while(true)
{
System.out.println("
}
classTestThreadimplementsRunnable//extendsThread
publicvoidrun()//线程的代码段,当执行start()时,线程从此出开始执行
System.out.println(Thread.currentThread().getName()+
"
运行的结果和前面一样。
5.1.4两种实现多线程方式的对比分析
既然直接继承Thread类和实现Runnable接口都能实现多线程,那么这两种实现多线程的方式在应用上有什么区别呢?
我们到底该用哪一个好呢?
为了回答这个问题,我们通过编写一个应用程序,来进行比较分析。
我们用程序来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程来表示。
我们首先这样编写这个程序:
ThreadDemo4.java
publicclassThreadDemo4
publicstaticvoidmain(String[]args)
ThreadTestt=newThreadTest();
t.start();
classThreadTestextendsThread
privateinttickets=100;
if(tickets>
0)
issalingticket"
+tickets--);
在上面的代码中,我们用ThreadTest类模拟售票处的售票过程,run方法中的每一次循环都将总票数减1,模拟卖出一张车票,同时该车票号打印出来,直到剩余的票数到零为止。
在ThreadDemo4类的main方法中,我们创建了一个线程对象,并重复启动四次,希望通过这种方式产生四个线程,结果怎样呢?
图5.4
从上面运行结果上,我们发现其实只有一个线程在运行,这个结果告诉我们:
一个线程对象只能启动一个线程,无论你调用多少遍start()方法,结果都只有一个线程。
我们接着修改ThreadDemo4,在main方法中创建四个ThreadTest对象:
newThreadTest().start();
这下达到我们的目的了吗?
图5.5
从上面结果上,我们看到的结果是每个票号都被打印了四遍,即四个线程各自卖各自的100张票,而不是去卖共同的100张票。
这种情况是怎样造成的呢?
我们需要的是,多个线程去处理同一资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个ThreadTest对象,就等于创建了四个资源,每个ThreadTest对象中都有100张票,每个线程在独立地处理各自的资源。
经过了这些曲折,这些实验,分析,我们可以总结出,要实现这个铁路售票模拟程序,我们只能创建一个资源对象(该对象中包含要发售的那100张票),但要创建多个线程去处理这同一个资源对象,并且每个线程上所运行的是相同的程序代码。
再回顾一下使用接口编写多线程的过程,大家应该能够自己写出这个程序了,请大家自己写完后,再来参看我下面给出的程序代码。
ThreadDemo5.java
publicclassThreadDemo5
newThread(t).start();
classThreadTestimplementsRunnable
System.out.println(Thread.currentThread().getName()
+"
在上面的程序中,我们创建了四个线程,每个线程调用的是同一个ThreadTest对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。
我们在Windows上可以启动多个记事本程序,也就是多个进程使用的是同一个记事本程序代码,明白了这个道理后,大家就应该对多个线程上运行完全相同的程序代码不再难以理解了。
可见,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
1、适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
2、可以避免由于Java的单继承特性带来的局限。
我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
3、有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。
多个线程可以操作相同的数据,与它们的代码无关。
当共享访问相同的对象时,即它们共享相同的数据。
当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
事实上,几乎所有多线程应用都可用第二种方式,即实现Runnable接口。
5.1.5后台线程与联合线程
1.后台线程与setDaemon方法
在上面售票系统的例子中,我们在main方法中创建并启动新的线程后,main方法便结束了,主线程也就随之结束了,这样的情况下,我们的这个java程序是否也随之结束呢?
从程序运行的结果上,我们已经看到,虽然main线程结束了,但整个java程序没有随之结束,对java程序来说,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束。
前台线程是相对后台线程而言的,我们按前面的方式产生的线程都是前台线程,如果我们对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程。
我们下面来看看,进程中只有后台线程运行时,进程就会结束的情况。
DaemonThread.java
publicclassDaemonThread
publicstaticvoidmain(String[]args)
Threadtt=newThread(t);
tt.setDaemon(true);
tt.start();
System.out.println(Thread.currentThread().getName()+"
isrunning."
图5.6
从上面的程序和运行结果上,我们看到:
虽然我们创建了一个无限循环的线程,因为它是后台线程,整个进程在主线程结束时就随之终止运行了。
这验证了进程中只有后台线程运行时,进程就会结束的说法。
2.联合线程与join方法
在讲到联合线程之前,我们先来看下面的这段程序。
JoinThread.java
publicclassJoinThread
Threadpp=newThread(t);
pp.start();
inti=0;
if(i==100)
{
try
{
pp.join();
}
catch(Exceptione)
System.out.println(e.getMessage());
mainThread"
+i++);
Stringstr=newString();
在上面的程序中用到了Thread类的join方法,即pp.join();
语句,它的作用是把pp所对应的线程合并到调用pp.join();
语句的线程中。
在main线程中的循环计数达到100之前,我们看到main线程和Thread-1线程交替执行的情况,如图5.7中打印出来的结果。
图5.7
在main线程中的循环计数达到100之后,我们看到只有Thread-1线程执行的情况,如图5.7中打印出来的结果。
图5.8
可见,Thread-1线程中的代码被并入到了main线程中,也就是Thread-1线程中的代码不执行完,main线程中的代码就只能一直等待。
查看JDK文档,我们发现,除了有无参数的join方法外,还有两个带参数的join方法,分别是join(longmillis)和join(long
millis,int
nanos),它们作用是指定合并时间,前者精确到毫秒,后者精确到纳秒,意思是两个线程合并指定的时间后,又开始分离,回到合并前的状态。
读者可以把上面的程序中的join方法修改成为有参数的,在看看程序运行的结果。
5.1.6多线程在实际中的应用
多线程的用途非常广泛,我给大家举几个应用多线程的例子,以便大家更好地理解和运用多线程。
1.当我们编写一个网络聊天程序时,如果使用的是单线程编程模式,通常的程序流程为图5.9:
图5.9
按上面的流程编写出来的程序将会产生这样的问题:
1)如果一方从键盘上读取了数据并发送给了对方,程序运行到“读取对方回送的数据”并一直等待对方回送数据,如果对方没有回应,程序不能再做其他任何事情,这时程序处于阻塞状态,即使用户想正常终止程序运行都不可能,更不能实现“再给对方发送一条信息,催促对方赶快应答”这样的事情了。
2)如果程序没有事先从键盘上读取数据并向外发送,程序将一直在“从键盘上读取数据”处阻塞,即使有数据从网上发送过来,程序无法到达“读取对方回送的数据”处,程序将不能收到别处先主动发送过来的数据。
我们用多线程模式编写这个网络聊天程序,其程序流程为图5.10所示:
图5.10
我们使用多线程模式编写这个程序后,发送过程与接收过程在两个不同的线程上运行,彼此之间没有任何约束,用户可以随心所欲地发送和接收数据了。
2.如果我们程序要将数据库一个表中的所有记录复制到另外一个表中,我们利用循环将源表中的记录逐一取出并插入到目标表中,当表中的记录有百万条以上,这个复制过程的时间将非常长,当我们想放弃这次复制,在单线程模式下,我们所能做的只有一直无奈地等到这个费时的操作完成或是做出类似关机一样的野蛮行为了。
我们可以用多线程编写这样的应用,由主线程负责表记录的复制,复制代码放在一个循环语句中,循环条件由一个boolean变量来控制。
如:
booleanbFlag=true;
while(bFlag)
复制程序
创建一个新的线程,该线程与用户交互,接收用户的键盘输入,当接收到用户的停止命令时,新线程将主线程的循环条件bFlag设置为假,即通知主线程在下次检
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程 操作