Java多线程讲义.docx

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

Java多线程讲义.docx

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

Java多线程讲义.docx

Java多线程讲义

Java多线程编程

1.线程的概念

首先我们从以下几个方面来了解线程的概念。

●进程和线程的区别

●线程的概念模型

●线程的运行状态

●线程的优先级

1.1进程和线程的区别

1.1.1多个进程

在大多数操作系统中都可以创建多个进程。

当一个程序启动时,它可以为即将开始的每个任务创建一个进程,并允许他们同时运行。

当一个程序因等待网络访问或用户输入而阻塞时,另一个程序还可以运行,这样就增加了资源利用率。

但是,按照这种方式创建每一个进程都要付出一定代价:

设置一个进程要占用相当一部分处理器时间和内存资源。

而且,大多数操作系统不允许进程访问其他的内存空间。

因此,进程间的通信非常不方便。

并且也不会将它自己提供给简单的编程模型。

如图10-1所示,Word、Excel、IE等每一个软件都可以独立启动,它们都代表一个独立的进程,而它们各自都有自己的内存空间,这些内存空间之间相互独立互不共享,各自也能够并行运行。

1.1.2多个线程

线程也为轻型进程(LWP)。

因为线程只能在单个进程的作用域内活动,所以创建线程比创建进程更廉价的多。

这样,因为线程允许协作和数据交换,并且在计算资源方面非常廉价,所以线程比进程更可取。

线程需要操作系统的支持,因此不是所有的机器都提供线程。

java编程语言,作为一种相当新的语言,已将线程和语言本身合为一体,这样就对线程提供了强健的支持。

如图10-2所示,线程1,线程2到线程n,它们都代表一个独立的线程,运行在一个进程中,因此它们拥有同一块内存区域,可以共享内存中的数据。

它们可以并行执行,但实际上在CPU的调用上是各自分割时间片的,并且是顺序执行的,如图中的粗线条所示。

1.2线程的概念模型

线程是彼此相互独立的、能独立运行的子任务,并且每一个线程都有自己的调用栈。

所谓的多任务是通过周期性地将CPU时间片切换到不同的子任务,虽然在微观上看来,单合CPU只能运行一个子任务,但从宏观上来看,每个子任务似乎是同时连续运行的。

我们现在使用的大多数操作系统都属于多任务、分时操行系统。

正是由于这种操作系统的出现才有了多线程这个概念,我们使用的windows、linux都属于此列。

1.2.1.1分时

什么是分时操作系统呢?

通俗点讲,就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还可以一边看网页?

实际上,并不是CPU在同时执行这些程序,CPU只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在CPU的高速计算能力,给人的感觉就好像是多个程序在同时执行一样。

一般可以在同一时间内执行多个程序的操作系统都有进程的概念。

一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。

在进程概率中,每一个进程内部数据和状态都是完全独立的。

因此可见创建一个进程并执行他的开销是很大的,所以线程出现了。

在Java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。

多线性意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。

线程与进程相似,是完成某个特定功能的一段代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。

所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weightprocess)。

一个进程中可以包含多个线程。

1.2.1.2多任务

多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建、运行到消亡的过程,称为线程的生命周期。

用线程的状态(state)表明线程处在生命周期的那个阶段。

线程有创建、可运行、运行中、阻塞、死亡5中状态。

通过线程的控制与调度可使线程在这个几种状态间转化每个程序至少自动拥有一个线程,称为主线程。

当程序加载到内存时,启动主线程。

1.3线程的运行状态

在java中,多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每一个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定那个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。

根据线程的调度过程,线程可以处以不同的执行状态,如图10-3所示,是线程的运行机制图。

线程的状态表示线程正在进行的活动及在此时间段内所能完成的任务。

线程有创建、可运行、运行中、阻塞、死亡5种状态。

一个具有生命的线程,总是处于这5种状态之一。

●新建状态:

使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程为创建状态(newThread)

●可运行状态:

使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)。

●运行状态:

Java运行系统通过调度选中一个Runnable的线程,使其占用CPU并转为运行中状态(Running),此时系统真正执行线程的run()方法。

●阻塞状态:

一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)。

●死亡状态:

线程结束后事死亡状态(Dead).

1.4线程的优先级

从概念上来说,线程是并发运行的,但是从计算机的运行角度来说,却是串行执行的。

当系统中只有一个CPU时,线程会以某一种顺序执行,这称作线程调度(scheduling).

Java采用的是一种简单、固定的调度法,即固定优先级调度。

这种算法是根据处于可运行态线程的相对优先级来实行调度。

当线程产生时,它继承原线程的优先级。

在需要时可对优先级进行修改。

在任何时刻,如果有多条线程等待运行,系统选择优先级最高的可运行线程运行。

只有当它停止、自动放弃或由于某种原因成为非运行态低优先级的线程才能运行。

如果两个程序拥有同样的优先级,它们将被交替地运行。

Java实时系统的线程调度算法还是强制性的,在任何时刻,如果一个比其他线程优先级都高的线程的状态变为可运行态,实时系统将选择该线程来运行。

同一时刻如果有多个线程处于可运行状态,则它们需要排队等待CPU资源。

此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度。

可运行状态的线程按优先级排队,线程调度优先级基础上的“先到先服务”原则。

线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度。

当线程调度管理器选中某个线程时,该线程获得CPU资源而进入运行状态,则这个线程立即被调度执行。

先式调度分为:

独占方式和分时方式。

●独占方式:

当前执行线程将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU或者CPU被一个更高优先级的线程占领。

●分时方式:

当前运行线程获得一个时间片,当时间到时,即使没有执行完也要让出CPU进入可运行状态,等待下一个时间片的调度,系统选中其他可运行状态的线程执行。

分时方式的系统使每个线程工作若干步,实现多线程同时运行。

2.线程的开发方法

线程所有的操作都发生在线程体中,在Java中线程体是从Thread类继承的run()方法,或实现Runnable接口的类中的run()方法。

当线程产生并初始化后,实时系统调用它的run()方法。

run()方法内的代码实现所产生线程的行为,它是线程的主要部分。

本小节将首先讲解Java程序中进行多进程调度的两种方法。

●使用Runtime类

●使用ProcessBuilder类

然后再讲解在Java中实现一个线程的两种方法。

第一种是实现Runnable接口实现它的run()方法。

第二种是继承Thread类,覆盖它的run()方法。

这两种方法的区别是,如果你的类已经继承了其他的类,那么你只能选择实现Runnabler接口了,因为Java只允许单继承。

2.1使用进程调用Java程序

一般我们在Java中运行其他类中的方法时,无论是静态调用,还是动态调用,都是在当前的进程中执行的,也就是说,只有一个Java虚拟机实例在运行。

而有的时候,我们需要通过Java代码去启动多个Java子进程。

这样虽然会占用一些系统资源,但会使程序更加稳定,因为新启动的程序是在不同的虚拟机进程中运行的,如果有一个进程发生异常,并不影响其他子进程。

在Java中,我们可以使用两种方法来实现这种要求。

最简单的方法就是通过Runtime中的exec()方法执行Java类名。

如果执行成功,则这个方法返回一个Process对象,如果执行失败,那么将抛出一个IOException错误。

下面让我们来看一个简单的例子。

(1)编写子进程程序

我们先编写一个Java程序,用来创建一个文件,如程序10-1所示。

publicclassFileTest{

publicstaticvoidmain(String[]args)throwsException{

Filefile=newFile("D:

/work/demo/newFile.txt");

file.createNewFile();

System.out.println("被调用成功了!

");

}

}

通过“javaFileTest”运行程序后,发现在D:

/demo下多了个newFile.txt文件,在控制台中出现“被调用成功!

”的输出信息

(2)使用Runtime调用子进程程序

下面是通过Runtime调用上面的Java程序的方式,如程序10-2所示。

publicclassRuntimeTest{

publicstaticvoidmain(String[]args)throwsIOException{

Runtimeruntime=Runtime.getRuntime();

runtime.exec("javaFileTest");

}

}

通过“Runtime”运行程序后,发现在D:

/demo下多个newFile.txt文件,但在控制台中并未出现“子进程输出:

被调用成功!

”的输出信息。

因此可以判定,FileTest已经被执行成功,但因为某种原因,FileTest的输出信息未在RuntimeTest的控制台中输出。

这个原因也很简单,因为使用exec()建立的是RuntimeTest的子进程,这个子进程并没有自己控制台,因此,它并不会输出任何信息。

(3)获取子进程程序的输出信息

如果想获取子进程的输出信息,可以通过Process中的getInputStream得到子进程的输出流(在子进程中输出,在父进程中就是输入),然后将子进程中的输出流从父进程的控制台输出。

具体的实现代码如程序10-3所示。

publicclassRuntimeTest{

publicstaticvoidmain(String[]args)throwsIOException{

Runtimeruntime=Runtime.getRuntime();

Processp=runtime.exec("javaFileTest");

BufferedInputStreambis=newBufferedInputStream(p.getInputStream());

BufferedReaderbr=newBufferedReader(newInputStreamReader(bis));

Strings;

while((s=br.readLine())!

=null){

System.out.println("控制台输出如下:

"+s);

}

bis.close();

br.close();

}

}

从上面的代码可以看出,在RuntimeTest.java中通过按行读取子进程的输出信息,然后再RuntimeTest中按行进行输出。

(4)给子进程传递输入信息

上面讨论的是如何得到子进程的输出信息。

那么,除了输出信息,还有输入信息。

既然子进程没有自己的控制台,那么输入信息也得由父进程提供。

我们可以通过Process的getOutStream()方法来为子进程提供输入信息(即由父进程向子进程输入信息,而不是由控制台输入信息),我们可以看看如下的代码。

为TestFile.java添加读入外部输入信息的代码:

BufferedReaderbr=newBufferedReader(newInputStreamReader(System.in));

System.out.println("由父进程输入的信息:

"+br.readLine());

添加向子进程输入的代码,如程序10-4所示。

publicclassRuntimeTest{

publicstaticvoidmain(String[]args)throwsIOException{

Runtimeruntime=Runtime.getRuntime();

Processp=runtime.exec("javaFileTest");

BufferedWriterbw=newBufferedWriter(

newOutputStreamWriter(p.getOutputStream()));

bw.write("Helloworld!

");

bw.flush();

bw.close();

BufferedInputStreambis=newBufferedInputStream(p.getInputStream());

BufferedReaderbr=newBufferedReader(newInputStreamReader(bis));

Strings;

while((s=br.readLine())!

=null){

System.out.println("控制台输出如下:

"+s);

}

bis.close();

br.close();

}

}

从以上代码可以看出,Test1得到由Test_Exec_In发过来的信息,并将其输出。

当你不加bw.flush()和bw.close()时,信息将无法到达子进程,也就是说子进程进入阻塞状态,但由于父进程已经退出了,因此,子进程也跟着退出。

如果要证明这一点,可以在最后加上System.in.read(),然后通过任务管理器(在Windows下)查看Java进程,你会发现如果加上bw.flush()和bw.close(),只有一个Java进程存在,如果去掉它们,就有两个Java进程存在。

这是因为,如果将信息传给Test2,在得到信息后,Test2就退出了。

在这里有一点需要说明一下,exec的执行是异步的,并不会因为执行的某个程序阻塞而停止执行下面的代码。

因此,可以在运行Test2后,仍可以执行下面的代码。

exec()方法经过多次的重载,上面使用的只是它的一种重载,它还可以将命令和参数分开,如exec(java.test2)可以写成exec("java","test2").exec还可以通过指定的环境变量运行不同配置的Java虚拟机。

(5)使用ProcessBuilder建立子进程

除了使用Runtime的exec()方法建立子进程外,还可以通过ProcessBuilder建立子进程,ProcessBuilder的使用方法如程序10-5的粗体部分所示。

publicclassProcessBuilderTest{

publicstaticvoidmain(String[]args)throwsIOException{

ProcessBuilderpb=newProcessBuilder("java","FileTest");

Processprocess=pb.start();

BufferedWriterbw=newBufferedWriter(newOutputStreamWriter(process.getOutputStream()));

bw.write("HelloWolrd!

");

bw.flush();

bw.close();

BufferedInputStreambis=newBufferedInputStream(process.getInputStream());

BufferedReaderbr=newBufferedReader(newInputStreamReader(bis));

Strings;

while((s=br.readLine())!

=null){

System.out.println("子进程输出如下:

"+s);

}

bis.close();

br.close();

}

}

在建立子进程上,ProcessBuilder和Runtime类似,不同的ProcessBuilder使用start()方法启动子进程,而Runtime使用exec()方法启动子进程。

得到Process后,它们的操作就完全一样了。

ProcessBuilder和Runtime一样,也可设置可执行文件的环境信息、工作目录等。

2.2第一种方法-继承Thread

Thread类是一个具体的类,即不是抽象类,该类封装了线程的行为。

publicclassThreadextendsObjectimplementsRunnable

线程是程序中的执行线程。

Java虚拟机允许应用程序并发地运行多个执行线程。

每个线程都有一个优先级,高优先级线程执行优先于低优先级线程。

每个线程可以标记为一个守护程序。

当某个线程中运行的代码创建一个新Thread对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当只创建线程是守护线程时,新线程才是守护程序。

当Java虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的main方法)。

Java虚拟机会继续执行线程,指定下一列任意情况出现时为止。

●调用了Runtime类的exit()方法,并且安全管理器允许退出操作发生。

●非守护线程的所有线程都已停止运行,无论是通过从对run()方法的调用中返回,还是通过抛出一个传播到run()方法之外的异常。

创建新执行线程有两种方法。

一种方法是将类声明为Thread的子类。

该子类应重写Thread类的run()方法。

接下来可以分配并启动该子类的实例。

例如,计算大于莫一规定值的质数的线程可以写出:

publicclassPrimeThreadTestextendsThread{

longminPrime;

publicPrimeThreadTest(longminPrime){

this.minPrime=minPrime;

}

publicvoidrun(){

//...

}

}

当使用继承创建线程时,可以这样启动线程:

newMyThread_1().start();

下例代码会创建并启动一个线程:

PrimeThreadp=newPrimeThread(143);

p.start();

程序10-6演示了创建两个线程对象的实例。

publicclassThreadTestextendsThread{

publicinttime;

publicStringuser;

publicThreadTest(inttime,Stringuser){

this.time=time;

this.user=user;

}

publicvoidrun(){

while(true){

try{

System.out.println(user+"休息"+time+"ms-"+newDate());

Thread.sleep(time);

}catch(InterruptedExceptione){

System.out.println(e);

}

}

}

publicstaticvoidmain(String[]args){

ThreadTestt1=newThreadTest(1000,"T1");

t1.start();

ThreadTestt2=newThreadTest(3000,"T2");

t2.start();

}

}

在本例中,我们可以看到一个简单的程序,它按两个不同的时间间隔(1秒和3秒)在屏幕上显示当前时间。

这个事通过创建两个新线程来完成的,包括main()共3个进程。

这里我们共创建了2个线程实例,分别间隔1秒和3秒输出一次字符串信息。

运行后控制台的输出如下:

从运行的结果来看,两个线程是并行运行的,先输出的是t1的内容。

如果要控制线程的执行顺序,那么可以为线程设置优先级:

t1.setPriority

(1);

t2.setPriority(Thread.MAX_PRIORITY);

这是优先执行t2,输出的结果如下:

2.3第二种方法-实现Runnable

上面我们使用Thread方法创建一个线程类,但是有时候要作为线程运行的类已经是某一个类的子类,Java中只允许单一继承,所有不能按照这种机制创建线程。

而Java同时为我们提供了解决的方法,即是在同一个类中实现任意数量的接口-Runnable接口。

publicinterfaceRunnable

Runnable接口应该由哪些打算通过莫一线程执行其实例。

类必须定义一个名为run()的无参方法。

voidrun();

在使用实现接口Runnable的对象创建一个线程时,启动该行程导致在独立执行的线程中调用对象的run()方法。

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

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

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

这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。

因此,创建线程的例子如下所示:

classPrimeRunimplementsRunnable{

longminPrime;

PrimeRun(longminPrime){

this.minPrime=minPrime;

}

publicvoidrun(){

//computeprimeslargerthanminPrime

}

}

当使用实现接口创建线程时,可以这样启动线程:

newThread(newMyTread_2()).start();

下列代码创建并启动一个线程;

PrimeRunp=newPrimeRun(14

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

当前位置:首页 > 求职职场 > 简历

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

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