第7章 Java多线程.docx

上传人:b****5 文档编号:12708529 上传时间:2023-04-21 格式:DOCX 页数:31 大小:45.17KB
下载 相关 举报
第7章 Java多线程.docx_第1页
第1页 / 共31页
第7章 Java多线程.docx_第2页
第2页 / 共31页
第7章 Java多线程.docx_第3页
第3页 / 共31页
第7章 Java多线程.docx_第4页
第4页 / 共31页
第7章 Java多线程.docx_第5页
第5页 / 共31页
点击查看更多>>
下载资源
资源描述

第7章 Java多线程.docx

《第7章 Java多线程.docx》由会员分享,可在线阅读,更多相关《第7章 Java多线程.docx(31页珍藏版)》请在冰豆网上搜索。

第7章 Java多线程.docx

第7章Java多线程

第7章Java多线程

核心内容:

1.多线程的概念以及与进程的区别

2.程序中创建线程的两种不同方法

3.线程的生命周期和控制

4.多线程的同步处理机制

7.1多线程的使用

面向对象编程使得程序划分成相互独立的模块执行。

但是还会碰到,不但需要把程序分解开来,而且还要让它的各个部分都能独立运行的问题。

这种能独立运行的子任务就是线程(Thread)。

我们可以认为线程都是独立运行的,有自己CPU的子任务。

实际上,是一些底层机制在为子任务分割CPU的时间,只是我们不知道。

单线程的执行就是老板从头到尾一个人做完,多线程就是老板在做的同时,拨出一部分事情让手下去做,这样会加快事情的进展。

7.2线程的概念

为了更好地理解什么是线程,我们有必要学习与之相关的概念——“进程”。

进程是指程序的一次动态执行过程,或者说进程是正在执行中的程序,其占用特定的地址空间。

以前古老的DOS操作系统是单任务的,系统每次只能做一件事。

比如你在复制文件的时候不能重命名文件。

现在的操作系统都是多任务操作系统,即将CPU的计算时间动态地划分给多个进程,每个运行的程序就是一个进程,比如听歌和QQ聊天可以同时进行,就是两个进程在同时工作。

如果使用的是windows系统,就可以在任务管理器中看到操作系统正在运行的进程信息。

相对于进程,线程是进程中一段连续的控制流程或是一段执行路径。

它不能独立存在,必须存在于某个进程中。

一个进程可以拥有多个线程,这些线程可以共享进程中的内存单元,可以访问相同的变量和对象,以实现线程间的通信,数据交换和同步操作等功能。

和进程相比,线程在管理费用,相互间通信等方面所需要的代价要小得多,所以也将线程称为“轻量级进程”。

进程和线程的区别主要体现在:

1.一个程序至少有一个进程,一个进程至少有一个线程;

2.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,极大地提高了程序的运行效率;

3.线程不能独立执行,必须依附在应用程序中,由应用程序提供多个线程执行控制。

多线程的目的为了最大限度的利用CPU资源,例如,通过浏览器既可以听音乐,又可以下载文件。

一般常见的Java应用程序都是单线程的。

多线程大大简化了“让一个程序同时做几件事”这个难题。

在多线程环境下,CPU会不断地在线程之间进行切换,给它们分配时间。

这样能大大提高效率,可以一边等数据,一边做其他事情。

Java语言就是第一个语言本身就支持线程的主流编程语言,其对线程的支持主要通过Thread类和Runnable接口来实现。

7.3线程的创建

在Java语言中,线程的创建通常有两种方式:

(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").start();

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

D:

\java\7>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;i<10;i++){

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()+"线程运行结束!

");

22}

23} 

程序运行结果:

 

main线程运行开始!

Thread-0线程运行开始!

main线程运行结束!

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}

程序运行结果:

D:

\java\7>javacExample7_3.java

D:

\java\7>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}

程序运行结果:

D:

\java\7>javacExample7_4.java

D:

\java\7>javaExample7_4

Thread-0issellingticket10

Thread-0issellingticket7

Thread-1issellingticket8

Thread-2issellingticket9

Thread-1issellingticket4

Thread-0issellingticket5

Thread-3issellingticket6

Thread-0issellingticket1

Thread-1issellingticket2

Thread-2issellingticket3

程序说明:

上面的程序中,创建了四个线程,每个线程调用的是同一个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}

程序运行结果:

D:

\java\7>javacExample7_5.java

D:

\java\7>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线程同步问题

线程同步是为了防止多个线程同时访问一个数据对象时,对数据造成的破坏。

例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1