Java程序设计-13-多线程.ppt
JavaJava程序设计程序设计第第1313章章 多线程开发多线程开发 学习目标学习目标理解多线程概念和运行机制理解多线程概念和运行机制能够区别能够区别ThreadThread和和RunnableRunnable两种方法创建线程的差异两种方法创建线程的差异掌握线程的并发访问处理机制,避免冲突和死锁掌握线程的并发访问处理机制,避免冲突和死锁理解线程的生命周期,掌握线程状态变换的条件理解线程的生命周期,掌握线程状态变换的条件基本概念u程序和进程u进程和线程uJava中的线程技术13.1创建线程继承了父类Thread,表明了它是一个线程类构造方法中的String类型的参数是作为线程的名字每个线程类都需要覆盖从父类Thread继承的run方法public class Clerk extends Thread public Clerk(String name)super(name);Overridepublic void run()super.run();/添加具体的工作代码添加具体的工作代码构造函数uThread()uThread(Runnable target)uThread(Runnable target,String name)uThread(String name)实现实现RunnableRunnable接口创建线程目标类接口创建线程目标类Thread类同样实现了Runnable接口。如果在run()方法运行时,需要获得当前执行线程的信息,可以使用Thread的类方法currentThread()获得。public class Clerk implements RunnableOverridepublic void run()/添加具体的工作代码添加具体的工作代码Thread clerk=new Thread(new Clerk();13.1.3 13.1.3 定义线程执行的任务定义线程执行的任务定制线程的任务体u要想使得创建的线程对象能够按照要求工作,还必须为它分派任务。就是重写线程类中的run()方法。u需要注意的是,这里定义的run()方法只是一个线程实例运行时会自动执行的任务代码,由虚拟机环境来寻找并执行run()方法中的任务代码,应用程序不要直接执行该方法。public class Clerk implements Runnable Override public void run()while(true)从排队机上获取一份新的业务从排队机上获取一份新的业务if 没有业务没有业务休息,直到得到业务办理通知休息,直到得到业务办理通知else判断业务类型,进行处理判断业务类型,进行处理处理完一笔,允许休息一定时间处理完一笔,允许休息一定时间 创建线程实例,执行任务创建线程实例,执行任务基于Runnable接口的实现类创建线程实例基于Thread及其子类创建实例Clerk clerk=new Clerk();Thread clerk=new Thread(new Clerk();启动线程clerk.start();13.2失控的线程如果一个程序运行中创建的多个线程互不影响,那么多线程的编程也许是一件很简单的事情,但现实往往比预想的复杂,多线程的编程困难之处在于线程间共享资源出现的协同工作问题。13.3线程间的同步和互斥互斥对象的访问u当多个线程同时对同一个对象状态进行更新时,就可能会导致更新异常。为了避免这种异常,Java语言提供了synchronized关键字用于对象更新的保护。usynchronized包含的代码块看做是“临界区”。在Java语言中,要想进入这个临界区,首先要获得受保护对象(如tran.getAccount()对象)的监视器(monitor)。u另外,当线程不能获得该对象的锁时,线程将会被阻塞,暂时停止运行,一直到它获得锁,然后继续运行程序。互斥方法的访问对同一个对象的方法互斥访问/*程序程序13-1 QueueMachine:增加了方法保护机制的排队机:增加了方法保护机制的排队机*/import java.util.LinkedList;public class QueueMachine private LinkedList queue=new LinkedList();private boolean isEmpty()return queue.isEmpty();public synchronized Transaction get()Transaction tran=queue.poll();return tran;public synchronized void add(Transaction tran)this.queue.add(tran);synchronized的作用u任何时候只能有一个线程访问一个对象里受此修饰符保护的方法。u当一个对象有多个synchronized修饰的方法时,任何时候只能有一个这样的受保护方法被一个线程所执行。u不同线程执行同一类型的不同对象中的受保护方法时,彼此互不影响。u附加上synchronized修饰符的方法,同一对象在一个时刻只能被一个线程所访问,可能会造成排队执行的现象,影响程序执行的性能。线程间的同步同步问题u什么是同步?u利用wait()/notify()/notifyAll()解决线程同步问题线程间的同步wait()方法u当对象执行该方法时,正在访问此对象的线程将被阻塞,暂时停止运行,直到其它线程执行这个对象的notify()或notifyAll()方法,唤醒它为止。唤醒后的线程应该继续对导致该线程被提醒的条件进行测试,如果不满足该条件,则继续等待。/访问互斥对象访问互斥对象synchronized(obj)while()obj.wait();./条件满足时的代码条件满足时的代码/访问互斥方法访问互斥方法synchronized type method()while()this.wait();./条件满足时的代码条件满足时的代码/银行职员线程调用银行职员线程调用get方法,从排队机获得一项业务方法,从排队机获得一项业务public synchronized Transaction get()hrows InterruptedException while(queue.isEmpty()this.wait();Transaction tran=queue.poll();return tran;notify()方法u唤醒在此对象监视器上等待的单个线程。如果多个线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意性的。直到当前线程放弃此对象上的锁定,被唤醒的线程才能继续执行。notifyAll()方法u唤醒在此对象监视器上等待的所有线程,这些线程经会重新请求此对象的监视器,获得后继续执行。同notify()一样,直到当前线程放弃此对象上的锁定,被唤醒的线程才能继续执行。/业务发生器线程调用业务发生器线程调用add方法,向排队机增加一项待处理业务方法,向排队机增加一项待处理业务public synchronized void add(Transaction tran)this.queue.add(tran);this.notifyAll();注意注意实践中使用wait-notify机制时需要注意以下几个问题。uwait()、notify()和notifyAll()方法均应置入临界区内的代码中,也就是说,当一个线程碰到此类方法时,但并没有获得该方法所在对象的监视器时,一个IllegalMonitorStateException异常将会被抛出。uwait()方法总是处在一个循环语句内,循环的作用在于检测是否拥有足够的资源一边继续运行线程,如果条件不满足,则线程将被置于该方法所属对象的监视器等待队列中。线程的死锁问题线程的死锁问题在多线程访问时,除了上述的线程冲突外,另外一种需要避免的就是著名的死锁了,如下面的代码就反映了典型的死锁关系。13.4线程的状态与转换新建就绪Clerk clerk1=new Clerk(queue,职员甲职员甲);u新建状态clerk1.start();u就绪状态就绪-运行u应用程序不能控制线程从就绪转为运行状态,这种状态的变化是由系统调度实现的。当解释器调度执行线程实例的run()方法后,线程就开始运行,状态从就绪状态转化为运行状态。u当线程运行时,会一直运行到结束,除非遇到阻塞条件或者分配的时间片用完,或者被暂停。因此,应当精心的组织线程的运行任务代码,使得它不至于长时间的占据处理机资源而导致其它线程无法工作,特别是在单处理机环境下运行就绪public static void yield()u调用Thread类的类方法yield(),此方法暂停当前正在执行的线程对象,并执行其他线程。u调用此方法时如果没有其它线程正在等待的话,线程实例将会立即获得重新执行的机会。u在分时系统中,当线程分配到的处理机运行时间片,或在抢占式调度策略下,高优先级的线程到来,将抢占低优先级线程获得的处理机而优先运行,这些都会导致线程从运行状态转为就绪状态。4 运行结束u在运行过程中,如果一切正常,通常会很快运行结束,因为一个线程的任务执行过程通常是一些比较关键的代码,不应耗费很长时间,否则就不足以应付高负载的业务请求。u在run()方法中的代码执行结束,线程就进入结束状态。u另外一种方法,通过调用线程对象的interrupt()方法,一个线程向另外一个线程发送消息告诉该线程应该停止运行。public void interrupt()5 运行阻塞u线程的运行并不总是一帆风顺。在运行的过程中,线程可能会遇到得不到足够的资源的情况,这些问题都会造成线程的阻塞 u还有一种情况,两个或多个线程间需要协同,才能保证程序运行的正确性。例如,一个线程运行需要另外一个线程提供资源,在资源不足的情况下,线程就需要停下来等待。(1)线程休眠public static void sleep(long millis)throws InterruptedExceptionu在线程的run()方法代码中的合适位置放置一条Thread.sleep()语句,执行该语句,将使线程状态置为阻塞(休眠)状态,直至苏醒。u该方法的参数即指定线程休眠的毫秒。如果线程休眠时间已到,线程就会被重新激活,并被置为就绪状态,等待获得处理机。u如果在休眠时,检测到了该线程的中断信号,将抛出InterruptedException异常,并清除该中断信号。u需要注意的是,只能在当前线程中执行sleep()方法,不能指定一个线程sleep,因为无法得知一个线程的准确状态,只有当前的线程正在运行,才可以使它休眠。u该方法的执行不会使当前线程丢失任何监视器的所属权。(2)等待u当线程实例执行任务代码时,向另一对象obj发出消息,执行某一方法的过程中,如果obj执行wait()方法,则使得该线程实例被阻塞,成为该obj对象的等待线程队列中的一个。3)连接线程u如果一个线程需要等待另外一个线程的消亡才可以继续执行,这是就可以用调用对方线程实例的join()方法。public final void join()throws InterruptedExceptionpublic final void join(long millis)throws InterruptedExceptiongenerator.join();/当前线程等待线程当前线程等待线程generator执行完后再继续往下执行。执行完后再继续往下执行。generator.join(1000);/当前线程等待线程当前线程等待线程generator1000毫秒。毫秒。如果两个工作需要同步,其中一个工作需要等待另一项工作的结果,则代码可以类似下面程序:B b=new B();/创建线程实例创建线程实例bb.start();/做工作做工作BA();/做工作做工作Ab.join();/当前线程等待线程当前线程等待线程B执行完成。执行完成。C();/继续工作继续工作C。6 阻塞就绪u被阻塞的主要原因有sleep()、wait()和join()。u由于休眠造成的阻塞在休眠时间结束,阻塞就会被解除转入到就绪状态等待被调度执行。u由于碰到了wait()而引起的阻塞,则需要相应的notify()或者notifyAll()来唤醒才可以。u由于join()造成的阻塞,则相应线程执行完毕或时间结束,即可进入就绪状态。表表132 Thread关于状关于状态变态变化的主要方法化的主要方法interrupt()中断此中断此线线程程interrupted()测试测试当前当前线线程是否已程是否已经经中断中断isAlive()测试线测试线程是否程是否处处于活于活动动状状态态isInterrupted()测试线测试线程是否已程是否已经经中断中断join()等待等待该线该线程程终终止止join(long millis)等待等待该线该线程程终终止的止的时间时间最最长为长为 millis 毫秒毫秒sleep(long millis)在指定的毫秒数内在指定的毫秒数内让让当前正在当前正在执执行的行的线线程休眠(程休眠(暂暂停停执执行)行)yield()暂暂停当前正在停当前正在执执行的行的线线程程对对象,并象,并执执行其他行其他线线程程wait()当前的当前的线线程等待程等待notify()唤唤醒在此醒在此对对象象监视监视器上等待的器上等待的单单个个线线程。如果所有程。如果所有线线程都程都在此在此对对象上等待,象上等待,则则会会选择唤选择唤醒其中一个醒其中一个线线程程13.5线程的管理1.线程的优先级u线程的优先级用数字来表示,范围从1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORITY。u一个线程的缺省优先级是5,即Thread.NORM_PRIORITY。uJava运行系统总是选择当前优先级最高的处于就绪状态的线程来执行。如果几个就绪线程由相同的优先级,将会用时间片方法轮流分配处理机。当线程被创建后,可通过下述方法改变线程的优先级uint getPriority();/得到线程的优先级uvoid setPriority(int newPriority);线程的中断利用线程对象的interrupt()方法来向该线程发出一个中断信号。public void interrupt()u通过发送中断消息的方式本身并不会停止线程的执行,只是在线程中设置了一个中断标记,表明了一个中断请求,这个标记如果生效,必须在run()方法中被检测u当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时将会抛出异常InterruptedException使用interrupted()方法检测线程的状态uThread提供了一个类方法interrupted()来检测当前线程是否已经中断。public static boolean interrupted()uinterrupted()方法会检查当前线程的中断状态,如果为“被中断状态”则改变当前线程为“非中断状态”并返回true,如果为“非中断状态”则返回false。利用isInterrupted()检查线程的状态uThread提供了一个实例方法isInterrupted()来检测当前线程是否已经中断。public boolean isInterrupted()uisInterrupted()方法是一个线程对象的测试方法,用来从线程外部验证该线程是否被中断了,并且不会改变它的状态。try Thread.sleep(rand.nextInt(200+rand.nextInt(3);catch(InterruptedException e)if(queue.isEmpty()working=closed;守护线程和用户线程守护线程或用户线程。upublic final void setDaemon(booleanon)u利用setDaemon(true)方法可以将该线程标记为守护线程,反之则是用户线程。u守护线程只是一个在后台运行的线程,从属于生成它的线程,一旦生成它的线程结束时,此守护线程也会一并结束。当正在运行的线程都是守护线程时,Java 虚拟机退出。u一个线程如果是由守护线程创建的,也默认是守护线程。u该方法必须在启动线程前调用,即在start()执行之前。u用户线程的运行独立于生成它的线程,也就是即使生成它的线程已经结束运行,用户线程还可以继续运行,直到结束如何使用u例如,一个服务器进程可以创建若干个守护进程监听用户请求,也可以创建若干个用户线程响应每个用户请求。监听线程应该是一个无限运行的任务程序,只要服务器进程正常运行,就应该一直保持监听状态,以便提供服务。当服务器进程决定关闭,那么作为守护进程会随着服务器进程的停止运行也结束自己的任务,但此时,作为用户线程的线程实例,也许还有任务没有执行完毕,如一个订单保存线程,那么强制关闭就可能造成数据丢失,这在实际环境中是不允许的,因此需要让这类任务线程能够运行直至正常结束。u一般而言,在有限时间内运行,承担事务处理功能的线程通常都会被创建为用户线程,以保证它们工作的完整性。13.5.4 13.5.4 线程组线程组线程组u通常一个应用中会创建很多做相同或不同任务的线程,为了能够集中管理这些线程,通常把担负相似任务的线程用线程组的方式统一管理起来,减少了管理的复杂程度。例如可以同时中断或者唤醒同一组里的所有线程。u简单的说,线程组表示一个线程的集合。public Thread(ThreadGroup group,Runnable target,String name);public Thread(ThreadGroup group,Runnable target);public Thread(ThreadGroup group,String name);例如例如ThreadGroup group=new ThreadGroup(clerk);Clerk clerk1=new Clerk(queue,职员甲职员甲,group);Clerk clerk2=new Clerk(queue,职员乙职员乙,group);表表132 ThreadGroup的主要方法的主要方法方法方法说明说明activeCount()返回此线程组中活动线程的估计数返回此线程组中活动线程的估计数enumerate(Thread list)把把此此线线程程组组及及其其子子组组中中的的所所有有活活动动线线程程复复制制到到指指定定数组中数组中getName()返回此线程组的名称返回此线程组的名称interrupt()中断此线程组中的所有线程中断此线程组中的所有线程