Java线程.docx

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

Java线程.docx

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

Java线程.docx

Java线程

第六章Java线程

【本章导读】

本章首先介绍了线程的基本概念,区别了线程和进程,接着介绍了线程的挂起方式,详细结实了线程的生命周期,然后介绍了线程的同步与死锁,详细解释了线程同步和死锁的概念及原因,并总结了多线程编程的一般规则,最后用案例一说明多线程的服务器编程,用案例二设计了一个时钟日历。

【本章要点】

线程的基本概念:

进程与线程

创建线程的方式:

继承Thread类

实现Runnable接口

线程的生命周期

线程的同步与死锁

6.1线程的基本概念

1进程

进程是并发执行的程序在一个数据集合上的执行过程。

对于多任务的操作系统Windows,我们可以同时打开和运行多哥应用程序。

每个独立运行的应用程序即为一个进程,同时运行的多个应用程序则为多进程。

对应用程序来说,进程就像一个大容器。

在应用程序被运行后,就相当于将应用程序装进容器里了,你可以往容器里加其他东西(如:

应用程序在运行时所需的变量数据、需要引用的DLL文件等),当应用程序被运行两次时,容器里的东西并不会被倒掉,系统会找一个新的进程容器来容纳它。

图6-1显示了Windows任务管理器中的进程。

图6-1Windows任务管理器中的进程

进程是由进程控制块、程序段、数据段三部分组成。

一个进程可以包含若干线程(Thread),线程可以帮助应用程序同时做几件事(比如一个线程向磁盘写入文件,另一个则接收用户的按键操作并及时做出反应,互相不干扰),在程序被运行后中,系统首先要做的就是为该程序进程建立一个默认线程,然后程序可以根据需要自行添加或删除相关的线程。

是可并发执行的程序。

在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单位,也是称活动、路径或任务,它有两方面性质:

活动性、并发性。

进程可以划分为运行、阻塞、就绪三种状态,并随一定条件而相互转化:

就绪--运行,运行--阻塞,阻塞--就绪。

2线程

线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。

图6-2给出了进程和线程之间的关系。

图6-2进程和线程之间的关系

作为进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

由于线程之间的相互制约,致使线程在运行中呈现出间断性。

线程也有就绪、阻塞和运行三种基本状态。

  线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。

具体而言,

1)多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响。

2)线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。

3)对线程的综合支持是Java技术的一个重要特色.它提供了thread类、监视器和条件变量的技术。

4)虽然Macintosh,WindowsNT,Windows9等操作系统支持多线程,但若要用C或C++编写多线程程序是十分困难的,因为它们对数据同步的支持不充分。

多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。

线程的运行中需要使用计算机的内存资源和CPU。

如图6-3所示,进程中多线程同时运行。

图6-3进程中的多线程同时运行

例如,我们使用浏览器时,可以在由浏览器打开的毕竟中打印页面,同时可滚动操作该页面,还可同时听音频文件和观赏动画图象。

这些任务中的每一个都是由线程完成的。

Java对线程有内部的支持。

Java体系结构的主要部分是多线程的,而且,Java程序中的线程大多允许applet同时接受用户的输入和显示另一动画,完成的这些任务就是采用了多线程。

使用多线程的优势:

1)减轻编写交互频繁、涉及面多的程序的困难.

2)程序的吞吐量会得到改善.

3)由多个处理器的系统,可以并发运行不同的线程.(否则,任何时刻只有一个线程在运行)

6.2创建线程的方式

Java中实现线程的方式有两种,一是生成Thread类的子类,并定义该子类自己的run方法,线程的操作在方法run中实现。

但我们定义的类一般是其他类的子类,而Java又不允许多重继承,因此第二种实现线程的方法是实现Runnable接口。

通过覆盖Runnable接口中的run方法实现该线程的功能。

基本的格式如下:

  ①继承Thread类,比如

  classMyThreadextendsThread{

  publicvoidrun(){

  //这里写上线程的内容

  }

  publicstaticvoidmain(String[]args){

  //使用这个方法启动一个线程

  newMyThread().start();

  }

  }

  ②实现Runnable接口

  classMyThreadimplementsRunnable{

  publicvoidrun(){

  //这里写上线程的内容

  }

  publicstaticvoidmain(String[]args){

  //使用这个方法启动一个线程

  newThread(newMyThread()).start();

  }

  }

说明:

一般鼓励使用第二种方法,应为Java里面只允许单一继承,但允许实现多个接口。

第二个方法更加灵活。

6.2.1Thread类

Java是通过java.lang包中的Thread类来实现的,可以构造和访问多线程中的各个线程。

该类支持许多种方法,得到关于线程的活动、集合的信息,并检查线程的性质,引起线程等待、中断和撤消。

通过扩展Thread类可使应用程序和类在单独的线程中运行。

  类Thread在包java.lang中定义,它的构造方法如下:

  publicThread();

  publicThread(Runnabletarget);

  publicThread(Runnabletarget,Stringname);

  publicThread(Stringname);

  publicThread(ThreadGroupgroup,Runnabletarget);

  publicThread(ThreadGroupgroup,Stringname);

  主要方法

  isActive()判断是否处于执行状态

  Suspend()暂停执行

  reSume恢复执行

  start()开始执行

  Stop()停止执行

  sleep()睡眠

  run()程序体

  yield()向其他线程退让运行权

例6-1继承Thread类,覆盖方法run(),我们在创建的Thread类的子类中重写run(),加入线程所要执行的代码即可。

 publicclassMyThreadextendsThread

{

intcount=1,number;

publicMyThread(intnum)

{

number=num;

System.out.println("Createthethread!

"+number);

}

publicvoidrun()

{

while(true)

{

System.out.println("Threads"+number+":

Count"+count);

if(++count==6)

return;

}

}

publicstaticvoidmain(String[]args)

{

inti;

for(i=0;i<5;i++)

newMyThread(i+1).start();

}

}

程序运行结果如图6-3:

图6-3程序6-1的运行结果

例6-2小应用程序中不用Runnable接口仍然可以使用线程(不调用主类的方法和调用主类的方法)。

---------------------------

//不调用主类的方法

importjava.applet.*;

publicclassthreadextendsApplet

{

mythreadt1=newmythread();

publicvoidinit()

{

t1.start();

}

classmythreadextendsThread

{

publicvoidrun()

{

for(inti=0;i<4;i++)

System.out.println(""+i);

{

try{

sleep(400);

}

catch(Exceptione)

{}

}

}

}

}

程序的运行结果入图6-4所示:

图6-4不调用主类的方法不用Runnable接口的示例结果

//调用主类的方法

importjava.awt.*;

importjavax.swing.*;

publicclassmainclassextendsJApplet

{

Ct1=newC(this);

publicvoidinit()

{

t1.start();

}

publicvoidpaint(Graphicsg)

{

g.drawString("Hello,java",10,50);

}

}

classCextendsThread

{

mainclassa;

C(mainclassb)

{

a=b;

}

publicvoidrun()

{

while(true)

{

a.repaint();

try{

sleep(400);

}

catch(InterruptedExceptione)

{}

}

}

}

程序运行结果如图6-5:

图6-5调用主类的方法不用Runnable接口的示例结果

6.2.2Runnable接口

在网上创建applet时,需要继承JApplet,因为Java不支持多重继承,不可同时继承JApplet类和Thread类。

因此,需要使用Runnable接口来解决这个问题。

Runnable由单个的run()方法组成,在线程激活时执行,可扩展JApplet类并实现Runnable接口和重新编码run()方法。

Runnable为非Thread子类的类也提供了一种激活方式。

通过实例化某个Thread实例并将自身作为运行目标,就可以运行实现Runnable的类而无需创建Thread的子类。

大多数情况下,如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。

Runnable接口只有一个方法run(),我们声明自己的类实现Runnable接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。

但是Runnable接口并没有任何对线程的支持,我们还必须创建Thread类的实例,这一点通过Thread类的构造函数

publicThread(Runnabletarget);

来实现。

例6-3Runnable接口运用的例子。

  publicclassMyThreadimplementsRunnable

  {

   intcount=1,number;

   publicMyThread(intnum)

   {

    number=num;

    System.out.println("创建线程"+number);

   }

   publicvoidrun()

   {

    while(true)

    {

     System.out.println

     ("线程"+number+":

计数"+count);

     if(++count==6)return;

    }

   }

   publicstaticvoidmain(Stringargs[])

   {

    for(inti=0;i〈5;i++)newThread(newMyThread(i+1)).start();

   }

  }

程序运行结果如图6-6所示。

图6-6程序6-3的运行结果

  严格地说,创建Thread子类的实例也是可行的,但是必须注意的是,该子类必须没有覆盖Thread类的run方法,否则该线程执行的将是子类的run方法,而不是我们用以实现Runnable接口的类的run方法,对此读者不妨试验一下。

  使用Runnable接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承Thread来得紧凑。

6.2.3线程的生命周期

一个线程产生后,就处于整个线程的某个状态。

图6-7描述了线程的生命周期。

图6-7线程的生命周期

线程的生命周周期有四个状态:

1、创建状态(NewThread)

已被创建但尚未执行(start()尚未被调用)。

根据实现线程的方法不同,创建新的线程对象有不同方法:

如果一个类继承了Thread类,那么这个类本身就是一个线程类,创建该类的对象即可。

创建一个新的线程对象之后,系统并不会为之分配资源,必须调用Thread类的start()方法来启动线程。

例如:

classsubThreadextendsThread

{

//重写run()方法的线程体

}

publicclassThreadDemo

{

publicstaticvoidmain(String[]args)

{

subThreadthread=newsubThread(“线程1”);

thread.start();

}

}

以Thread的常用构造函数为例来说明线程:

publicThread(Runnabletarget,Stringname);其中的target是实现线程体的目标对象,必须是Runnable接口类的对象,name是线程的名称。

2、可执行状态(Ruunable)

线程可以执行,虽然不一定正在执行。

CPU时间随时可能被分配给该线程,从而使得它执行。

start()方法负责启动线程,用于分配线程所必须的系统资源,调度线程运行,调用线程的run()。

当调用start()方法时,线程进入可运行转台。

线程处于可运行状态,并不就是正在运行。

因为单处理机不可能依次执行一条以上的线程,处理器维持一个线程队列。

一旦启动,线程排队等候处理机时间,等待论到其执行,在任何时刻,线程可能在等待处理机的处理。

这就是线程的状态是可运行而非正在运行的原因。

对于优先级相同的线程,线程等待处理的概率是相同。

说明:

线程的优先级

  线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得CPU时间时,线程调度系统根据各个线程的优先级来决定给谁分配CPU时间,优先级高的线程有更大的机会获得CPU时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。

  可以调用Thread类的方法getPriority()和setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。

一旦启动线程,线程进入可运行状态,调用run()方法。

该方法一般包含一个循环,否则,线程会失去其存在的意义。

3、不可运行状态(NotRunnable):

正常情况下run()返回使得线程死亡。

调用stop()或destroy()亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。

如果线程处于下列状态之一,我们称之为不可运行状态:

正在睡眠sleep();

正在等待wait();

被另一个线程所阻塞。

用sleep()方法可使线程进入睡眠状态,一个睡眠状态的线程在指定时间过去后,可进入运行状态。

在指定时间里,线程是不可运行的。

publicstaticvoidsleep(longmillis)throwsInterruptedException

参数millis是线程睡眠的时间,为毫秒数。

如例6-2程序中的sleep(400);该方法是静态方法,属于在当前的线程上操作,而且会抛出InterruptedException异常,在调用该方法时,必须处理这种异常。

4、阻塞状态(Dead):

线程不会被分配CPU时间,无法执行,也称为死亡状态。

一个线程可以是自然死亡,也可以是被杀死。

当线程完成run()方法中的循环时,则线程是自然死亡。

例如程序6-1中:

publicvoidrun()

{

while(true)

{

System.out.println("Threads"+number+":

Count"+count);

if(++count==6)

return;

}

}

run()方法中的循环变量有6次自增,此线程的生命周期为循环的6次自增。

因此,当一个线程执行完所有语句后就自动终止,我们可以调用线程的stop()方法,也可以强制终止线程。

如果希望线程正常终止,可采用标记来使线程中的run()方法退出。

在Thread类中,方法isAlive()可用来确定线程是否启动或停止。

根据线程的生命周期,我们可以总结出线程在运用使的操作步骤如下:

1.publicclassmythreadextendsAppletimplementsRunnable

(小应用或已经是某个类的子类时)

2.继承类Thread

publicclassmythreadextendsThread

3.上述两种方法中都可用类Thread产生线程的对象Threadnewthread;

4.创建并启动线程

newthread=newThread(this);

newthread.start();

5.run方法是运行线程的主体,启动线程时,由java直接调用publicvoidrun()

6.停止线程,由小应用程序的stop调用线程的stopnewthread.stop()

7.sleep方法的作用,暂停线程的执行,让其它线程得到机会,sleep要丢出异常,必须抓住.

try{

sleep(1000);

}

catch(InterruptedExceptione)

{

}

6.3线程的同步与死锁

6.3.1同步问题的提出

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

例6-4:

两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。

classFoo

{

privateintx=100;

publicintgetX()

{

returnx;

}

publicintfix(inty)

{

x=x-y;

returnx;

}

}

publicclassMyRunnableimplementsRunnable

{

privateFoofoo=newFoo();

publicstaticvoidmain(String[]args)

{

MyRunnabler=newMyRunnable();

Threadta=newThread(r,"Thread-A");

Threadtb=newThread(r,"Thread-B");

ta.start();

tb.start();

}

publicvoidrun()

{

for(inti=0;i<3;i++)

{

this.fix(30);

try{

Thread.sleep

(1);

}

catch(InterruptedExceptione)

{

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+":

当前foo对象的x值="+foo.getX());

}

}

publicintfix(inty)

{

returnfoo.fix(y);

}

}

程序运行结果如图6-8所示:

图6-8程序6-4的运行结果

说明:

从结果发现,这样的输出值明显是不合理的。

原因是两个线程不加控制的访问Foo对象并修改其数据所致。

如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。

这样就能保证Foo对象中数据的合理性了。

在具体的Java代码中需要完成一下两个操作:

把竞争访问的资源类Foo变量x标识为private;

同步那些修改变量的代码,使用synchronized关键字同步方法或代码。

我们可以从一个经典的银行取款问题了解同步与死锁的含义。

有一个银行账户,还有余额1100元,现在A通过银行卡从中取1000元,而同时另外一个人B通过存折也从这个账户中取1000元。

取钱之前,要首先进行判断:

如果账户中的余额大于要取的金额,则可以执行取款操作,否则,将拒绝取款。

我们假定有两个线程来分别从银行卡和存折进行取款操作,当A线程执行完判断语句后,获得了当前账户中的余额数(1000元),因为余额大于取款金额,所以准备执行取钱操作(从账户中减去1000元),但此时它被线程B打断,然后,线程B根据余额,从中取出1000元,然后,将账户里面的余额减去1000元,然后,返回执行线程A的动作,这个线程将从上次中断的地方开始执行:

也就是说,它将不再判断账户中的余额,而是直接将上次中断之前获得的余额减去1000。

此时,经过两次的取款操作,账户中的余额为100元,从账面上来看,银行支出了1000元,但实际上,银行支出了2000元

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

当前位置:首页 > 高等教育 > 其它

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

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