Java语言程序设计 第七部分 多线程
多线程 第一讲 进程和线程
进程 进程的定义 进程的属性 进程(process)是一个可并发执行的具有独立功能的应用程序,是操作系统进行资源分配和保护的基本单位。 结构性 进程包含了数据集合和运行于其上的程序。每个进程至少由三要素组成: 程序块、数据块和进程控制块。 共享性 同一程序同时运行于不同数据集合上时构成不同的进程,或者说,多个不同的进程可以共享相同的程序。 动态性 进程是程序在数据集合上的一次执行过程,是动态概念,它有生命周期。
进程 进程的属性 独立性 进程既是系统中资源分配和保护的基本单位,也是系统调度的独立单位。 并发性 进程可以并发地执行,进程的并发性能够提高资源利用率和系统效率。 制约性 并发进程之间存在着制约关系,进程在进行的关键点上需要相互等待或互通消息,以保证程序执行的可再现性和计算结果的唯一性。
进程的状态 一个进程从创建至撤销的整个生命期间,有时占用处理器执行,有时虽可运行但分不到 处理器,有时虽有空闲处理器但因等待某个事件的发生而无法执行。这一切都说明进程与程序不相同,进程是活动的且有状态变化的。 进程状态 就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行。 运行(running)态:进程占用处理器正在运行。 阻塞(blocked)态:进程不具备运行条件,正在等待某个事件的完成。 一个进程创建后将处于就绪状态。每个进程在执行过程中,任一时刻当且仅当处于上述三种状态之一。一个进程在执行过程中,它的状态将发生改变。
线程 引入线程机制的动机和思路 操作系统采用进程机制使得多个程序能够并发执行,提高了资源使用率和系统效率。在早期操作系统中,进程是系统进行资源分配的基本单位,也是处理器调度的基本单位,进程在任一时刻只有一个执行控制流,这种结构的进程称为单线程进程。单线程进程调度时存在进程时空开销大、进程通信代价大、进 程并发粒度粗、不适合于并行计算等问题,操作系统引入线程机制来解决这些问题。 线程机制的基本思路是,把进程的两项功能——“独立分配资源”和“被调度分派执行” 分离,前一项任务仍由进程完成,后一项任务交给称为线程的实体完成。这样,进程作为系统资源分配与保护的独立单位,不需要频繁地切换;线程作为系统调度和分派的基本单位, 会被频繁地调度和切换。
线程 线程的定义 线程( thread)是操作系统进程中能够独立执行的实体(控制流) ,是处理器调度和分派 的基本单位。线程是进程的组成部分,每个进程内允许包含多个并发执行的线程。同一个进程中的所有线程共享进程获得的主存空间和资源,但不拥有资源。 线程1 线程2 …. 线程n 进程1 进程2 进程3…. 进程n
线程的状态 由于线程是处理器调度和分派的基本单位,线程有生命周期,线程的状态有五种:新建、就绪、运行、等待、终止。
线程的并发性 在单处理器上,一组线程的执行在时间上是重叠的,它们的操作是交叉执行的,称这些 线程是并发执行的。 从宏观上看,并发性反映出一个时间段中有几个线程都处于运行还未运行结束状态,且这些线程都在同一处理器上运行; 从微观上来开,对于单CPU计算机而言,任在一时刻仅有一个线程 在处理器上运行。并发的实质是一个处理器在多线程之间的多路复用,并发是对有限的物理 资源强制行使多用户共享,消除计算机部件之间的互等现象,以提高系统资源利用率。
线程调度 当有多个线程处于就绪态时,它们排队等待处理器资源。对于单处理器而言,任一时刻 只有一个线程能够占用处理器运行。因此,按照什么原则决定就绪队列中的哪个线程能获得 处理器则是操作系统的重要问题,这就是线程调度的任务。 线程调度采用剥夺方式,当一个线程正在处理器上执行时,操作系统可以根据规定的原 则剥夺分配给它的处理器,而把处理器分配给其他线程使用。常用的剥夺原则有两种:一是 高优先级线程可以剥夺低优先级线程运行;二是当运行线程时间使用完后被剥夺处理器。剥夺式策略可以避免一个线程长时间独占处理器,能给线程提供较好的服务。
并发程序设计 顺序程序设计 顺序程序设计方法是,把一个程序设计成一个顺序执行的程序模块,不同程序也是按序执行的。顺序程序设计具有如下特性: 执行的顺序性。 环境的封闭性。 执行结果的确定性。 计算结果的可再现性。 顺序程序设计的顺序性、封闭性、确定性和再现性表明了程序与计算(程序的执行〉是一一对应的,给程序的编制、调试带来很大方便,其缺点是计算机系统效率不高。
并发程序设计 并发程序设计 并发程序设计方法是,使一个程序分成若干个可同时执行的程序模块,每个程序模块和它执行时所处理的数据就组成一个进程。 采用并发程序设计技术构造的一组程序模块在执行时具有如下特性: 并发性:进程的执行在时间上可以重叠,在单处理器系统中可以并发执行;在多处理器系统中可以并行执行。 共享性:它们可以共享某些变量,通过引用这些共享变量就能互相交换信号,程序 的运行环境不再是封闭的。 制约性:进程并发执行或协作完成同一任务时,会产生相互制约关系,必须对它们并发执行的次序加以协调。
并发程序设计 并发程序设计 采用并发程序设计技术构造的一组程序模块在执行时具有如下特性: 交互性:由于进程共享某些变量,所以一个程序的执行可能影响其他程序的执行结果,因此这种交互必须是有控制的,否则会出现不正确的结果。即使程序自身能正确运行,由于程序的运行环境不再是封闭的,程序结果仍可能是不确定的,计算过程具有不可再现性。 采用并发程序设计的目的是充分发挥硬件的并行性,消除处理器和I/O设备的互等现象,提高系统效率。计算机部件能并行工作仅仅有了提高效率的可能性,而计算机部件并行工作的实现还需要软件技术去利用和发挥。
多线程 第二讲 多线程的创建
Runnable接口与Thread类 Java支持内置的多线程机制,它提供了创建、管理和控制线程对象的重要方法。 之前我们所写的Java程序运行时,进程由main()方法开始执行,进程中只有一个线程,就是main线程,也称为主线程。当在main()方法中创建多个线程对象并启动线程对象执行时,进程中则包含了多个线程在并发地执行。 Java的java.lang.Thread类用于创建和控制线程。线程对象由Thread类或其子类声明,线程对象执行的方法是java.lang .Runnable接口中的run()方法。就像应用程序必须从main()开始执行一样,一个线程必须从run()方法开始执行。
Runnable接口 Runnable接口 Runnable接口约定抽象方法run()是线程的执行方法。一个线程对象必须实现run()方法 以描述该线程的所有活动及执行的操作,己实现的run()方法称为该线程对象的线程体。 public interface Runnable { void run(); }
Thread线程类 public class Thread extends Object implements Runnable { public Thread() //构造方法 public Thread(String name) //name指定线程名 public Thread(Runnable target) //target指定线程的目标对象 public Thread(Runnable target, String name) public void run() //描述线程操作的线程体 public final String getName() //返回线程名 public final void setName(String name) //设置线程名 public static int activeCount() //返回当前活动线程个数 public static Thread currentThread() //返回当前执行线程对象 public Sting toString() //返回线程的字符串信息 public void start() //启动已创建的线程对象 }
创建线程 继承Thread类 我们可以建立一个类 Thread的子类,通过继承类 Thread并覆盖其 run()方法来构造线程体。
class SimpleThread extends Thread{ public SimpleThread(String str){ super(str); } public void run(){ for(int i=0;i<10;i++){ System.out.println(i+" "+getName()); try{ sleep((int)(Math.random()*1000)); }catch(InterruptedException e){} System.out.println("DONE!!"+getName());
class TwoThreadsTest{ public static void main(String args[]) { new SimpleThread("Jamaica").start(); new SimpleThread("Fuji").start(); }
创建线程 实现Runnable接口 通过建立一个实现了Runnable接口的对象,并以它作为线程的目标对象也可以构造线程 体。 在具体应用中,采用哪种方法来构造线程体要视具体情况而定。因为Java不允许多重继承,通常,当一个线程已继承了另一个类时,只能用第二种方法来构造,即实现 Runnable接口。
class PingPong implements Runnable{ String word; int delay; PingPong(String whatToSay,int delayTime){ word=whatToSay; delay=delayTime; } public void run(){ try{ for(;;){ System.out.print(word+" "); Thread.sleep(delay);
}catch(InterruptedException e){ return; } public static void main(String[] args){ Runnable ping=new PingPong("Ping",33); Runnable pong=new PingPong("PONG",100); new Thread(ping).start(); new Thread(pong).start();
线程对象的生命周期 新建态 使用new运算符创建一个线程对象(new Thread)后,该线程仅仅是一个空对象,系统 没有为它分配资源,该线程处于新建态(NEW)。 就绪态和运行态 从操作系统角度看,处于新建态的线程启动后,进入就绪态,再由操作系统调度执行而 成为运行态。由于线程调度由操作系统控制和管理,程序无法控制。所以,从程序设计角度 看,线程启动后即进入运行态(RUNNABLE),程序中不需要区分就绪态或运行态。 进入运行态的线程对象,系统执行线程对象的run()方法。
线程对象的生命周期 阻塞态和等待态 一个运行态的线程因某种原因不能继续运行时,进入阻塞态(BLOCKED)或等待态。 处于阻塞态或等待态的线程不能执行,即使处理器空闲也不能执行。只有当引起阻塞的原因 被消除,或等待的条件满足时,线程再转入运行态,重新进入线程队列排队等待运行,再次 运行时将从暂停处继续运行。 导致线程进入阻塞态或等待态的原因有多种,如输入/输出、等待消息、睡眠、锁定等。 等待态两种WAITING和TIMED_WAITING, WAITING的等待时间不确定, TIMED_ WAITING等待时间确定。
线程对象的生命周期 终止态 线程对象停止运行未被撤销时是终止态(TERMINATED )。导致线程终止有两种情况: 运行结束或被强行停止。 当线程对象的run()方法执行结束时,该线程对象进入终止态,等待系统撤销对象所占用的资源; 当进程因故停止运行时,该进程中的所有线程将被强行终止。
线程对象的生命周期
Thread类中改变和判断线程状态的方法 线程启动 public synchronized void start() //启动己创建的线程对象 public final boolean isAlive() //返回线程是否启动的状态 线程睡眠 Sleep()方法使当前线程睡眠若干毫秒,线程由运行态进入等待态,不可运行,睡眠时间到时线程可再次进入运行态。 public static void sleep(long millis) throws InterruptedException 线程中断 public void interrupt() //设置当前线程对象运行中断标记 public boolean isInterrupted() //判断线程是否中断 public static boolean interrupted()//判断线程是否中断
示例 设计滚动字演示线程状态及改变方法 WelcomeJFrame.java
线程对象的优先级 Java提供10个等级的线程优先级,分别用1~10表示,优先级最低为1,最高为10, 默认值为5。 Thread类中声明了3个表示优先级的公有静态常量: public static final int MIN__PRIORITY=1 //最低优先级 public static final int MAX_PRIORITY=10 //最高优先级 public static final int NORM_PRIORITY=5 //默认优先级 Thread类中与线程优先级有关的方法有以下2个: public final int getPriority() //获得线程优先级 public final void setPriority(int newPriority)//设置线程优先级
多线程 第三讲 线程的同步机制
交互线程 并发线程之间可能是无关的,也可能是交互的。 无关的并发线程是指它们分别在不同的变量集合上操作。一个线程的执行与其他并发线 程的进展无关,即一个并发线程不会改变另一个并发线程的变量值。 交互的并发线程是指它们共享某些变量,一个线程的执行可能影响其他线程的执行结果,交互的并发线程之间具有制约关系。 交互线程并发执行时相互之间会干扰或影响其他线程的执行结果,因此交互线程间需要有同步机制。 交互线程间存在两种关系 竞争关系的交互线程间需要采用线程 互斥方式解决共享资源冲突问题。 协作关系的交互线程间需要采用线程同步方式解决线程问 通信及因执行速度不同而引起的不同步问题。
线程间的竞争关系 同一个进程中的多个线程由系统调度而并发执行时,彼此之间没有直接联系,但是,如果两个线程要访问同 一资源,则线程间存在资源竞争关系,这是线程间的间接制约关系。一个线程通过操作系统 分配得到该资源,另一个将不得不等待,这时一个线程的执行可能影响到同其竞争资源的其 他线程。 在极端的情况下,被阻塞线程永远得不到访问权,从而不能成功地终止。 资源竞 争出现了两个问题:一个是死锁(deadlock)问题,一组线程如果都获得了部分资源,还想要得到其他线程所占用的资源,最终所有的线程将陷入死锁;另一个是饥饿( starvation)问 题,一个线程由于其他线程总是优先于它而被无限期拖延。
线程互斥和临界区管理 线程互斥(mutual exclusion)是解决线程间竞争关系的手段。线程互斥是指若干个线程 要使用同一共享资源时,任何时刻最多允许一个线程去使用,其他要使用该资源的线程必须等待,直到占有资源的线程释放该资源。 把共享变量代表的资源称为临界资源,并发线程中与共享变量有关的程序段称为临界区。由于与同一变量有关的临界区分散在各有关线程的程序 段中,而各线程的执行速度不可预知,操作系统对共享一个变量的若干线程进入各自临界区有以下3个调度原则: 一次至多一个线程能够在它的临界区内。 不能让一个线程无限期地留在它的临界区内。 不能强迫一个线程无限期地等待进入它的临界区。特别地,进入临界区的任一线程不能妨碍正等待进入的其他线程的进展。
Java的线程互斥实现 操作系统提供“互斥锁”机制实现并发线程互斥地进入临界区,对共享资源进行操作,程序员只需要在程序 中声明哪个程序段是临界区即可。 Java提供关键字synchronized声明一段程序为临界区。 synchronized有两种用法 声明一条语句、声明一个方法。
Java的线程互斥实现 同步语句 ,使用synchronized声明一条语句为临界区,该语句称为同步语旬,语法格式如下: 其中,(对象)是多个线程共同操作的公共变量,即需要被锁定的临界资源,它将被互斥地使用; (语句)是临界区,它描述线程对临界资源的操作,如果是多条语句,需要用{ } 括起来成为一条复合语句。 一个同步语句允许一个对象锁保护一个单独的语句(也包括一个复合语句) ,在执行这 个语句之前,必须要获得这个对象锁。
Java的线程互斥实现 同步语句执行过程如下: 当第1个线程希望进入临界区执行(语句)时,它获得临界资源即指定(对象)的 使用权,并将对象加锁,然后执行语句对对象进行操作。 在此过程中,如果有第2个线程也希望对同一个对象执行这条语句,由于作为临界资源的对象己被锁定,则第2个线程必须等候。 当第1个线程执行完临界区语旬,它将释放对象锁。 之后第2个线程才能获得对象的使用权并运行。
Java的线程互斥实现 同步方法 使用synchronized声明一个方法,该方法称为同步方法,语法格式如下: { 方法体 } public synchronized char pop(){ while(index==0){ try{ System.out.println("pop wait!!!"); this.wait(); }catch(InterruptedException e){} } this.notify(); index--; return buffer[index];
Java的线程互斥实现 同步方法 同步语句与同步方法的行为基本相似,只是前者的作用范围小,它只是锁住一条语句(或复合语句)而不是完整的方法,它还指定所要获得锁的对象,增 加了灵活性并且缩小了对象锁的作用域。 public synchronized char pop(){ while(index==0){ try{ System.out.println("pop wait!!!"); this.wait(); }catch(InterruptedException e){} } this.notify(); index--; return buffer[index];
线程间的协作关系与线程同步 线程间的协作关系 当一个进程中的多个线程为完成同一任务而分工协作时,它们彼此之间有联系,知道其 他线程的存在,而且受其他线程执行的影响。这些线程间存在协作关系,这是线程间的直接 制约关系。由于合作的每一个线程都是独立地以不可预知的速度推进,这就需要相互协作的 线程在某些协调点上协调各自的工作。当合作线程中的一个到达协调点后,在尚未得到其伙 伴线程发来的信号之前应阻塞自己,直到其他合作线程发来协调信号后方被唤醒并继续执行。 这种协作线程之间相互等待对方消息或信号的协调关系称为线程同步。
线程间的协作关系与线程同步 线程间的协作关系 示例:发送线程与接收线程。 Buffer.java
线程间的协作关系与线程同步 线程同步 解决方法 线程同步( synchronization)是解决线程间协作关系的手段。线程同步指两个以上线程基 于某个条件来协调它们的活动。一个线程的执行依赖于另一个协作线程的消息或信号,当一 个线程没有得到来自于另一个线程的消息或信号时则需等待,直到消息或信号到达才被唤醒。 解决方法 wait() notify() notifyAll()
死锁 解决方案 减少锁定时间 按次序访问对象 退出机制 A B App1 App2
public class SyncStack{ private int index=0; private char []buffer=new char[6]; public synchronized char pop(){ while(index==0){ try{ System.out.println("pop wait!!!"); this.wait(); }catch(InterruptedException e){} } this.notify(); index--; return buffer[index]; public synchronized void push(char c){ while(index==buffer.length){ try{ System.out.println("push wait!!!"); this.wait(); }catch(InterruptedException e){} } this.notify(); buffer[index]=c; index++;
public class Producer implements Runnable{ SyncStack theStack; public Producer(SyncStack s){ theStack=s; } public void run(){ char c; for(int i=0;i<20;i++){ c=(char)(Math.random()*26+ 'A'); theStack.push(c); System.out.println("Produced: "+c); try{ Thread.sleep((int)(Math.random()*100)); }catch(InterruptedException e){}
public class Consumer implements Runnable{ SyncStack theStack; public Consumer(SyncStack s){ theStack=s; } public void run(){ char c; for(int i=0;i<20;i++){ c=theStack.pop(); System.out.println("Consumed: "+c); try{ Thread.sleep((int)(Math.random()*1000)); }catch(InterruptedException e){}
public class SyncTest{ public static void main(String args[]){ SyncStack stack=new SyncStack(); Runnable source=new Producer(stack); Runnable sink=new Consumer(stack); Thread t1=new Thread(source); Thread t2=new Thread(sink); t1.start(); t2.start(); }