• 1. 多线程10/24/2018iSoftStone Technologies Ltd.
  • 2. 9.1线程的基本概念9.1.1 进程和线程 9.1.2 线程的生命周期和状态2©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 3. 9.1.1 进程和线程进程是一个执行中的程序。系统可以同时创建多个进程,也就是说,系统可以同时完成多个任务。对每一个进程来说,它拥有自己独立的内存空间、数据等运行中需要的系统资源,它的状态和拥有的资源是完全独立的 线程是一个程序中实现单一功能的一个指令序列,一个程序中可以包含若干个线程。或者简单说,线程是轻量级的进程 进程存在一些问题。例如,如果一个程序要同时完成多项任务,如果把这些任务都设计成一个个进程,当系统频繁地进行进程切换(术语称为进程调度)时,就要占用大量的系统资源(主要是CPU时间)。线程是解决这类问题的一个途径 多线程是指一个进程中同时运行的多个完成不同子任务的线程。多线程不仅可以使一个程序同时完成多项任务,而且为此消耗的系统资源也比进程方法少许多 3©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 4. 9.1.2 线程的生命周期和状态线程的生命周期是指线程从创建、运行到死亡的过程。在一个线程的生命周期内,可以有五种状态,即创建、可运行、运行中、不可运行(或称阻塞)和死亡状态。通过对线程的控制和调度,一个线程可以在这五种状态之间转换 Java 语言支持多线程程序的运行,要编写多线程的程序,需要Java提供的支持线程的类和接口 线程生命周期的状态图如图10.1所示 4©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 5. 9.2 Thread(线程)类和Runnable(可运行)接口Thread(线程)类和Runnable(可运行)接口提供了支持线程的功能。由于Thread类和Runnable接口是在java.lang(语言)包中,所以使用Thread类和Runnable接口时不需要使用import导入语句 9.2.1 Thread(线程)类 9.2.2 Runnable(可运行)接口5©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 6. 9.2.1 Thread(线程)类Thread类是Java中支持多线程机制的一个重要类, 它提供了很多控制和调度线程的方法。这些方法包括:启动、睡眠、运行等 (1)成员变量 l        MIN_PRIORITY 线程可以拥有的最小优先级 l        MAX_PRIORITY 线程可以拥有的最大优先级 l        NORM_PRIORITY 线程默认的优先级 (2)构造方法 l        Thread() l        Thread(Runnable target) l        Thread(Runnable target,String name) l        Thread(String name) l        Thread(ThreadGroup group,Runnable target) l        Thread(ThreadGroup group,String name)6©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 7. (3)常用方法l        static int activeCount() 返回线程组当前线程的数目 l        static Thread currentThread() 返回当前运行线程的引用 l        void destroy() 撤销线程(过时) l        String getName() 返回线程名称 l        int getPriority() 返回线程优先级 l        ThreadGroup getThreadGroup() 返回当前线程所属的线程组 l        void interrupted() 中断当前线程 l        boolean isAlive() 是否正在运行 l        void join() 等待当前线程运行结束 l        void run() 运行线程 l        void setName(String name) 设置线程名称为name l        void setPriority(int newPriority) 设置线程优先级 l        static void sleep(long millis) 指定当前运行线程休眠时间 l        void start() 启动线程 l        Static void yield() 让当前运行线程临时退出 TestSimpleThread.java7©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 8. 9.2.2 Runnable(可运行)接口Java中的Runnable接口为设计多线程程序提供了另外一种方法。任何实现Runnable接口的类都支持多线程。实际上Thread类就实现了Runnable接口。 在Runnable接口中只有一个方法:run()。只要在程序中实现该方法,并把想要运行的线程代码放入这个方法中就可以了 要设计线程程序,用继承Thread类方法比用实现Runnable接口方法更简单,因为用继承Thread类方法设计时,因为已经继承了Thread类,所以定义的对象可以直接调用Thread类的方法;而用实现Runnable接口方法设计时,要使用Thread类提供的方法必须定义Thread类的对象,即通过Thread类的对象来调用Thread类的方法。TestSimpleRunnable.java8©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 9. Thread类和Runnable接口相比,Runnable接口有哪些好处呢?一、可以多个线程要访问同一资源,此时代码与数据是独立的,例如出售车票; 二、可以避免java单继承带来的局限性; 例如一个类要继承另一个类又要实现多线 程; 例如: 9©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 10. 9.3 线程的状态和状态控制9.3.1 线程的生命周期和状态 9.3.2 线程分组 9.3.3 线程的优先级10©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 11. 9.3.1 线程的生命周期和状态线程的生命周期是指一个线程从产生到消亡的发展过程。在这个过程中,每一个时刻都是线程生命周期的一个阶段。我们把线程生命周期的不同阶段称作线程的不同状态 可以用Thread类中提供的方法和Object类提供的wait()和notify()方法在程序中改变线程的状态 图10.2描述了线程的状态变化以及引起状态变化的原因 11©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 12. 线程的状态1. 创建状态(new Thread) 通过继承Thread类或实现Runnable接口,可以定义一个具有线程功能的类,再使用new运算符就可以创建一个线程对象。例如,执行下面语句后,线程就处于创建状态: Thread myThread = new MyThreadClass( ); 2. 可运行状态(Runnable) 运行一个线程必须具备两个条件:(1)可以使用的CPU;(2)运行线程的代码和数据。运行线程的代码和数据由线程对象在run方法中提供。CPU则由线程调度程序分配。在调用了新创建线程对象的start()方法后,线程就处于可运行状态 可运行状态的线程可能会同时有很多,这些可运行状态的线程在一个优先级队列中排队,等待操作系统的线程调度程序调度。 可运行状态的线程来自下列三种情况之一: (1)一个线程创建完毕; (2)处于运行状态线程的时间片到; (3) 处于阻塞状态的线程的阻塞条件解除12©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 13. 线程的状态3. 运行状态(Running) 线程调度程序按照一定的线程调度原则,从等待运行的处于可运行状态的线程队列中选择一个,使它获得CPU的使用权。这时,该线程就从可运行状态变为了运行状态 处于运行状态的线程将首先执行run()方法 4. 不可运行状态(not Runnable) 不可运行状态也称为阻塞状态(Blocked)。因为某种原因系统不能执行线程的状态称为不可运行状态。这时就是处理器空闲也不能执行该线程 进入不可运行状态的原因通常有: (1)线程调用了sleep()方法; (2)线程调用了Object类的wait()方法; (3)输入输出流中发生了线程阻塞。 5. 死亡状态(Dead) 有两种情况使线程处于死亡状态: (1)自然消亡,即run()方法执行完;(2)应用程序停止运行13©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 14. 9.3.2 线程分组每一个线程都是一个线程组的成员。线程分组提供了一个统一管理多个线程而不需单独管理的机制。例如,当多个线程分为一组时,可以用一个方法启动或挂起线程组中的所有线程。Java语言的java.lang包中的ThreadGroup子包提供了实现线程分组的类 在应用程序中,当建立一个线程时,如果不指定线程组,系统会把这个线程放入缺省线程组中去。缺省线程组的组名是main 图10.3是一个线程组和它的成员线程 14©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 15. ThreadGroup类的构造方法和常用方法(1)构造方法 l        ThreadGroup(String name) l        ThreadGroup(ThreadGroup parent, String name) 其中,name是线程组名,parent是父线程组名,默认值是当前线程组 (2)常用方法 l        String getName() 返回当前线程组名 l        int activeCount() 获得本组活动线程的数目 l       int enumerate(Thread[] list) 将本组活动线程拷贝到数组list中 EnumerateTest.java15©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 16. 9.3.3 线程的优先级线程有优先级。线程调度程序在进行线程调度时要考虑线程的优先级。另外,线程的执行先后顺序还与操作系统的线程调度方式有关 线程的优先级用数字来表示,范围从1到10,即MIN_PRIORITY 的值最小为1,MAX_PRIORITY的值最大为10。一个线程的默认优先级是5,即没有给线程优先级赋值时线程优先级NORM_PRIORITY值为5。 (1)Thread类中关于优先级的静态成员变量 l    static final int NORM_PRIORITY 线程默认优先级 l    static final int MIN_PRIORITY 最低优先级 l        static final int MAX_PRIORITY 最高优先级 (2)Thread类中对优先级进行操作的方法 l    int getPriority() 返回线程的优先级 l    void setPriority(int newPriority) 设置线程的优先级为newPriority TestThreadPrio.java 注意:线程得运行与操作系统线程调度有关,所以有一定得不确定性.16©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 17. 9.4 线程间的互斥在实际软件系统中,许多线程之间有时会共享一些数据,并且它们之间的状态、行为是相互影响的。线程间共享的数据以及线程状态、行为的相互影响有两种:一种是线程间的互斥,一种是线程间的同步 10.4.1 共享资源问题 10.4.2 互斥线程的设计方法17©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 18. 9.4.1 共享资源问题共享资源是指在程序中并行运行的若干个线程操作的数据资源相同 在程序中,并行运行的若干个线程操作共享资源时可能出错。例如,在一个民航的售票系统中,一个售票处工作人员(甲)欲给一个客户分配一个座位,于是调出飞机的座位分配图,但当甲察看座位分配图时,另外一个售票处的工作人员(乙)也在为其他客户分配座位,乙也调出了这张飞机座位分配图。乙为客户分配了座位6A,同时在图中标记此座位(6A)已被分配。此时,甲还在察看那张未被更新的座位分配图,甲并不知道座位分配图已经发生了变化(座位6A已被分配),甲也为客户分配了座位6A。这时问题就出现了,一个座位被两个人同时拥有 那么,问题出在哪里呢?显然是飞机的座位分配图,这是一个共享的数据。在一个工作人员使用它时,应该禁止其他工作人员使用它 这类问题实际是一种生产者-消费者问题。生产者一次或多次提供货物,若干个消费者同时消费。我们定义这种生产者-消费者问题是生产者-消费者模式Ⅰ TestQueue.java18©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 19. 共享资源的互斥基于上述分析,应当认识到,当并行运行的几个线程操作共享资源,且问题的模型属于生产者-消费者模式Ⅰ时,一定要保证操作的互斥,即一个共享资源每次只能由一个线程操作,当这个线程还没有操作完时,不允许其他线程操作共享资源。只有这样才能保证并行运行的多个线程对共享资源操作的正确性19©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 20. 9.4.2 互斥线程的设计方法互斥锁是基于共享资源的互斥性设计的,用来标记那些多个并行运行的线程共享的资源。被用互斥锁标记的共享资源,在一个时间段内,只能有一个线程使用;只有当加互斥锁的线程使用完了该共享资源,另一个线程才可以使用。这样就可以保证线程对共享资源操作的正确性 Java语言中,关键字synchronized 就是用来给共享资源加互斥锁的。当某个共享资源被用synchronized 修饰时,就表明该共享资源在某段时间内只能由加锁的线程访问 20©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 21. 为共享资源加互斥锁的两种方法:(1)锁定一个对象和一段代码 声明格式为: synchronized (〈对象名〉) { <语句组> } 对象表示要锁定的共享资源,一对花括号内的语句组表示锁定对象期间执行的语句。此格式可以用来在一个线程的一部分代码上加互斥锁 当一个线程执行这段代码时,就锁定了指定的对象 这就形成了多个线程对同一个对象的“互斥”使用方式,因此该对象也称为互斥对象 (2)锁定一个方法 声明格式为: synchronized 〈方法声明〉 { 〈方法体〉 } 这里虽然没有指出锁定的对象,但是一个方法必然属于一个类,因此,此格式锁定的是该方法所属类的对象,锁定的范围是整个方法 21©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 22. 9.5 线程间的同步还有一种线程间对共享资源的相互影响,是一个线程的运行要依赖于另一个线程对共享资源的处理结果,这称为线程间的同步 10.5.1 资源同步问题 10.5.2 同步线程的设计方法22©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 23. 9.5.1 资源同步问题一个计算机模拟生产-销售问题。规定:(1)由于仓库有限,必须把仓库中的商品销售完后才能再生产;(2)由于担心不能及时供货被罚款,只有当仓库中有货时才能销售。在这类问题中,两个操作(生产和销售)中的任何一个操作执行的前提,是另一个操作已经完成。另一个操作已经完成的标志是共享资源(仓库)满足本操作的执行条件。 这样一类问题也称作生产者-消费者问题。我们定义这种生产者-消费者问题是生产者-消费者模式Ⅱ。消费者操作执行的前提条件是生产者操作已经生产了;生产者操作执行的前提条件是消费者已经消费了。 在存在共享资源同步问题的程序设计中如果不考虑共享资源的同步问题,必然引起错误 TestStorage.java23©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 24. 线程同步的设计方法线程同步的设计方法,是除了加互斥锁外,还要在并行运行的线程上加信号量 信号量是一个标志,表示一种操作(如生产或销售)是否已执行完,另一种操作(如销售或生产)是否可以执行了。 Object类提供了一组关于线程同步的方法: l        void wait() 引起当前线程等待 l        void wait(long time) 引起当前线程等待 l        void notify() 唤醒一个正在等待这个对象的线程 l        void notifyAll() 唤醒所有正在等待这个对象的线程 wait()方法的所谓等待,是把当前线程从运行状态转为阻塞状态 notify()方法的所谓唤醒,是把等待线程从阻塞状态转为可运行状态,从而有机会让线程调度程序调度进入运行状态 还有一点要说明的是:wait()方法所在的代码段一定要加互斥锁synchronized。这是因为:wait()方法在把当前线程从运行状态转为阻塞状态后,还要释放互斥锁锁定的共享资源,这样的操作不允许中间被打断,这显然是共享资源的互斥问题 Storage.java TestStorage.java24©2005 iSoftStone Technologies Ltd. All rights reserved.
  • 25. 谢谢!10/24/2018iSoftStone Technologies Ltd.