1、JavaJava高级程序设计高级程序设计第第6 6章章 JavaJava多线程多线程6.1 应用场景6.2 相关知识6.3 实施过程6.4 拓展知识6.5 拓展训练6.6 课后小结6.7 课后习题6.8 上机实训6.1 应用场景计算机可以同时完成多项任务,称为并发。并发完成的每个任务就是一个独立线程。在网络分布式、高并发应用程序的情况下,Java多线程编程技术在很多开发工作中得到非常广泛的应用。经典的多线程问题:1.火车票预订问题。假定火车票有10000张,现有10个售票点。每个售票窗口就像一个线程,它们各自运行,共同访问相同的数据火车票的数量。由于多个线程并发地执行,访问共享同一数据,会出现
2、数据不一致的现象,所有必须要用同步锁synchronized,保证某一时刻只能有一个线程执行售票功能。2.经典生产者与消费者问题。生产者不断的往仓库中存放产品,消费者从仓库中消费产品。其中生产者和消费者都可以有若干个。在这里,生产者是一个线程,消费者是一个线程。仓库容量有限,只有库满时生产者不能存放,库空时消费者不能取产品,这就是线程的同步。3.客户机/服务器通信问题。假如Server只能接受一个Client请求,当第一个Client连接后就占据了这个位置,后续Client不能再继续连接。采用多线程机制,当Server每接受到一个Client连接请求之后,都把处理流程放到一个独立的线程里去运行
3、,然后等待下一个Client连接请求,这样就不会阻塞Server端接收请求了。6.2 相关知识6.2.1 相关概念6.2.2 线程的创建和启动6.2.3 线程的生命周期6.2.4 线程的管理6.2.1 相关概念进程是指运行中的应用程序,每个进程都有自己独立的内存空间。一个应用程序可以同时启动多个线程。几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。一个进程可以由多个线程组成,即在一个进程中可以同时运行多少不同的线程,他们分别执行不同的任务。当进程内的多个线程同时运行时,
4、这种运行方式被称为并发运行。进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。进程的特征特征动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。并发性:任何进程都可以同其他进程一起并发执行独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;异步性:由于进程间的相互制约,使进程具有执行的间断性,即
5、进程按各自独立的、不可预知的速度向前推进结构特征:进程由程序、数据和进程控制块三部分组成。多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创
6、建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。简单的说:一个程序运行后至
7、少有一个进程,一个进程里可以包含多个线程,但至少包含一个线程。多线程(multithreading):是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬
8、件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。在实际的应用中,多线程是非常有用的,一个浏览器必须能同时下载多个图片:一个Web服务器必须能同时响应多个用户请求;Java虚拟机本身就在后台提供一个超级线程来进行垃圾回收。总之,多线程在实际编程中的应用是非常广泛的。6.2.2 线程的创建和启动 线程的创建有两种方式:通过继承Thread类来和通过实现Runnable接口来创建。1.继承Thread类创建线程类通过继承Thread类创建线程类的具体步骤和具体代码如下:定义一个继承Thread类的子类,并重写该类的run()方法;创建Thread子类的实例,即创建了线程对象;调用该线程
9、对象的start()方法启动线程。2.实现Runnable接口创建线程类通过实现Runnable接口创建线程类的具体步骤和具体代码如下:定义Runnable接口的实现类,并重写该接口的run()方法;创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。调用该线程对象的start()方法启动线程。6.2.3 线程的生命周期 线程在它的生命周期中会处于不同的状态。当线程被创建后,它要经过新建、就绪(可运行)、运行、阻塞和死亡五种状态。尤其是当线程启动后,它不能一直处于运行状态,所以线程要在多条线程之间进行转换,于是线程会在多次运行
10、、阻塞之间进行转换。状态的转换如图6-2-3所示11、新建状态 2、就绪状态3、运行状态 4、阻塞状态5、死亡状态6.2.4 线程的管理 1.join线程Thread类提供了让一个线程等待另一个线程完成的方法:join()方法。当某个程序执行流中调用了其他线程的join()方法的时候,调用线程将被阻塞,直到join方法假如的join线程执行完成为止。6.3 实施过程任务一 Java多线程并发控制Java多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变
11、量的唯一性和准确性。任务一、模拟火车票售票任务需求分析:模拟火车站售票大厅进行火车票售票。任务程序分析:1.票数要使用同一个静态值2.为保证不会出现卖出同一个票数,要java多线程同步锁。任务分析设计:1.创建一个售票窗口类TicketWindow,实现Runable接口,重写run方法,在run方法里面执行售票操作!售票要使用同步锁:即有一个窗口卖这张票时,其他窗口要等这张票卖完!2.创建售票大厅类TestTicketLobby,主方法创建四个售票窗口,创建线程,并启动线程,开设售票。其类图如图6-3-7所示。6.4 拓展知识补充两个知识点:1、wait和notify方法wait/notif
12、y/notifyAll这三个方法是Object类定义的this.wait():让当前线程进入休眠状态,直到被其他线程调用notify/notifyAll唤醒this.notify()/notifyAll():唤醒由当前对象调用wait进入休眠状态的线程;notify()随机唤醒一个,notifyAll()唤醒所有。2、synchronized关键字锁对象:在线程体中,如果由于使用的资源被其他线程访问会导致数据错误,那么我们在只用这个资源之前,可以通过synchronized关键字将这个共享对象锁住,使用完成之后进行释放。锁方法:使用synchronized关键字修饰一个方法,表示对这个方法实现
13、同步,即不允许多个线程同时调用这个方法。实际上方法被同步指的是调用当前方法的对象被锁住。如果使用synchronized锁方法,那么调用这些方法的对象必须是同一个对象。6.5 拓展训练任务:Java模拟生产者-消费者问题。任务需求分析:生产者生产产品,存放在仓库里,消费者从仓库里消费产品。任务程序分析:1、生产者仅仅在仓储未满时候生产,仓满则停止生产。2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。3、当消费者发现仓储没产品可消费时候会通知生产者生产。4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。分析设计:类图如图6-5-9所示。图6-5-9 类图TestProducerC
14、onsumer是主类,Producer生产者,Consumer消费者,Product产品,Storehouse仓库6.6 课后小结 1.线程的创建java中线程的创建主要有两种方式:1、继承Thread类(Thread中方法主要用于针对线程本身的处理);2、实现Runnable接口;2.线程的通讯线程之间的通讯实际上是线程之间传递参数的问题。3、往线程中传递参数:通过新建线程的构造函数;4、线程执行中,往外界传递参数:5、通过方法的返回值,但可能线程未执行完成,所以返回NULL。所以,用轮询的方式,获取方法返回值。缺点是浪费CPU周期。6、通过注册回调方法,在构造线程的时候传入回调类,然后,在
15、线程执行过程中调用回调方法。7、JDK1.5,提供了Future、Callable和Executor,Executor子类ExecutorService创建线程,实现Callable接口作为回调方法(实现call()方法),返回一个Future类。3.线程同步当多线程共享资源时,必须考虑同步问题。可以用synchronized关键字标注关键对象或方法。但是,同步不仅影响性能,同步的越多越容易造成死锁问题。(死锁:两个线程想要独占某种资源,但是,两者同时占用这种资源的子集的情况)。同步的替代方法:1、尽可能使用局部变量而不是字段,基本类型传参是值传递,是线程安全的(String也是安全的,一旦创
16、建不能更改);2、构造函数一般不需要考虑线程安全问题;3、将非线程安全的类作为线程安全类的一个私有字段;4、注:多线程中使用System.out输出,也属于共享资源。4.线程的调度JVM的线程调度器,抢占式的(preemptive)和协作式的(cooperative)。由于一个线程长时间占用CPU,会造成其他线程的饥饿状况。可以通过设置线程的优先级来改变这种情况,但是,在相同优先级的线程中,需要使用如下6种类方法,手动控制:1、对I/O阻塞;2、放弃,调用Thread.yield(),提供给相同优先级的线程CPU的使用机会;3、休眠,sleep(),提供给相同优先级及以下优先级CPU的使用机会
17、;4、连接线程,join(),等待某个指定线程执行结束,或者执行一段时间;5、等待某个对象,wait(),放弃对一个对象的锁定并暂停(之前的方法并不会放弃资源);6、结束,方法返回return;6.7 课后习题三、简答题1.什么事进程和线程?两者的区别是什么?2.创建线程有哪两种方法?它们各自的优缺点是什么?3.简单描述线程的生命周期。4.线程的优先级范围是什么?如何实现优先级?5.为什么多线程中要引入同步机制?Java中如何实现线程的同步?6.线程的调度有哪些方法?各有什么功能?6.8 上机实训实训一、使用多线程编程,模拟银行账户,两个以上的用户同时进行存取操作,要求:1、余额大于取款金额才可取钱2、多人存取完毕后,余额正常提示:当一个用户对金额进行修改时,其他用户应不可进行修改实训二、使用多线程编程,实现素数的判定。待判定的整数由键盘录入后存放在一个列表中,创建10个线程从列表中取出整数进行判定,判定的结果存入到另一个列表中,用户可以通过键盘查询判定的结果。