500 likes | 616 Views
JAVA 多线程操作. 信息学院 李 峰. 内容提纲. 1 线程的概念 2 线程的状态 3 如何创建线程 4 线程的调度及其控制方法 5 多个线程如何进行互斥和同步. 学习目标. 如何采用 java 线程实现多道程序设计 学习 java 中线程的使用,掌握线程的调度和控制方法,理解多线程互斥和同步的实现原理和机制,以及多线程的应用。 掌握线程间的调度关系,尤其是通过线程睡眠机制使其它等待线程获得执行机会。. 难点和重点. 多线程的调度和控制 多线程的互斥和同步. 1 多线程基本概念. 输入输出装置. 文件. 各种系统资源.
E N D
JAVA多线程操作 信息学院 李 峰
内容提纲 1 线程的概念 2 线程的状态 3 如何创建线程 4 线程的调度及其控制方法 5 多个线程如何进行互斥和同步
学习目标 • 如何采用java线程实现多道程序设计 • 学习java中线程的使用,掌握线程的调度和控制方法,理解多线程互斥和同步的实现原理和机制,以及多线程的应用。 • 掌握线程间的调度关系,尤其是通过线程睡眠机制使其它等待线程获得执行机会。
难点和重点 • 多线程的调度和控制 • 多线程的互斥和同步
1 多线程基本概念 输入输出装置 文件 各种系统资源 输入输出装置 文件 各种系统资源 数据空间 数据空间 程序区段 程序区段 同时有数个地方在执行 只有一个地方在执行 传统的进程 多线程的任务
1 多线程基本概念 • 一个线程就是一个程序内部的顺序控制流。 • 线程与进程的区别: 1 每个进程的内部数据和状态都是完全独立的,进程切换的开销大。 2 线程,轻量级的进程,同一类线程共享一块内存空间和一组系统资源, 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。 3 多进程, 在操作系统中,能同时运行多个任务。 4 多线程,在同一个应用程序中,有多个并发的顺序流同时执行。
1 多线程基本概念 多线程的优势: • 减轻编写交互频繁、涉及面多的程序的困难 • 程序的吞吐量会得到改善,能提高资源使用效率 • 由多个处理器的系统,可以并发运行不同的线程.(否则,任何时刻只有一个线程在运行)
1 多线程基本概念 线程体 • Java的线程是通过java.lang.Thread类来实现的。 • 每个线程都是通过某个特定Thread对象的run()方法来完成其操作的,方法run()称为线程体。 • class MyThread extends Thread {public void run() {// 这里写上线程的内容}
2 线程的状态 • 创建状态(new):线程对象已经创建,但尚未启动,所以不可运行。 • 可运行状态(runnable):所有资源都准备好了,就差cpu资源,一旦线程调度器分配cpu资源给该线程,立刻开始执行。 • 死亡状态(dead):线程体执行完毕,即run()方法运行结束。 • 堵塞状态(not runnable):不仅缺乏cpu资源,还缺乏其它资源。
suspend() sleep() yield() new Thread() wait() . . start() Runnable . Not Runnable New Thread stop() or run()exit resume() stop() stop() Dead 2 线程状态
2 线程状态 • 创建状态(new Thread) Thread myThread=new MyThreadClass() (注意: MyThreadClass 是Thread的子类) • 可运行状态(Runnable) Thread myThread=new MyThreadClass() myThread.start( ) • 死亡状态(Dead) 自然撤消(线程执行完毕)
2 线程状态 • 下面几种情况下,当前线程会放弃cup,进入阻塞状态(Not runnable): (1) 线程调用sleep()方法主动放弃执行权; (2) 由于当前线程进行I/O访问,外存读写,等待用户输入等操作,导致线程阻塞; (3) 为等候一个条件变量,线程调用wait()方法 (4) 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。
3 线程的创建 • Thread(ThreadGroup group,Runnable target, String name) • Thread(Runnable target, String name) • Thread(Runnable target) • Thread(String name) • Thread()构造新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。 • 任何实现接口Runnable的对象都可以作为一个线程的目标对象。
3 线程的创建 创建线程的两种方法: 1 定义一个线程类,它继承类Thread并重写其中的方法run(); 2 提供一个实现接口Runnable的类作为线程的目标对象,把目标对象传递给这个线程实例,由该目标对象提供线程体run()。
3 线程的创建 通过继承类Thread构造线程 public class ThreadDemo1extends Thread { public ThreadDemo1(String str) { super(str); } public void run() { for (int i = 0; i<5; i++) { System.out.println(i+" "+getName()); try{ sleep((int)(Math.random()*1000)); }catch(InterruptedException e) { } } System.out.println("end!"+getName() ); } }
public class ThreadTest { public static void main(String[] args) { Thread T1=new ThreadDemo1("First"); Thread T2=new ThreadDemo1("Second"); T1.start(); T2.start(); } } 执行结果: 0 Second 0 First 1 First 2 First 1 Second 2 Second 3 First 3 Second 4 Second 4 First end!First end!Second
3 线程的创建 通过实现接口构造线程 public class MyThread implements Runnable { public int time; public String name; public MyThread(int time, String str) { this.time=time; this.name=str; } public void run() { for (int i=0;i<4;i++){ System.out.println(" "+i+"= "+this.name); try{Thread.sleep(this.time);} catch(InterruptedException e){ } } }}
public class ThreadTest2 { public static void main(String[] args) { Thread mt1=new Thread(new MyThread(400,"rod")); Thread mt2=new Thread(new MyThread(1000,"bos")); mt1.start(); mt2.start(); } } 执行结果: 0= rod 0= bos 1= rod 2= rod 1= bos 3= rod 2= bos 3= bos
3 线程的创建 两种方法的比较: 1 使用Runnable接口,可以从其他类继承,保持程序风格的一致性; 2 直接继承Thread类,不能再从其他类继承,但编写简单,可以直接操纵线程。
4 线程的调度和控制方法 4.1线程的调度 • Java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪些线程来执行。 • 线程调度是抢先式的,按照优先级来调度: (1)时间片方式 (2)非时间片方式
4 线程的调度和控制方法 4.2 线程的优先级 某一时刻只有一个线程在执行,调度策略为固定优先级调度。线程的优先级用数字来表示,范围从1到10,级别有: MIN-PRIORITY 1 NOM_PRIORITY 5 缺省优先级 MAX-PRIORITY 10 Thread.setPriority(Thread.MIN_PRIORITY) int Thread.getPriority();
public class ThreadDemo2 extends Thread { String name; public ThreadDemo2(String str) { this.name=str; } public void run() { for (int i = 0; i<3; i++) { System.out.println(this.name+" "+i+"="+getPriority()); } } }
public class ThreadTest2 { public static void main(String[] args) { Thread T1=new ThreadDemo2("T1"); Thread T2=new ThreadDemo2("T2"); ThreadDemo2 T3=new ThreadDemo2("T3"); T1.setPriority(1); T2.setPriority(10); T3.setPriority(10); T1.start(); T2.start(); T3.start(); } } 执行结果: T2 0=10 T2 1=10 T2 2=10 T3 0=10 T3 1=10 T3 2=10 T1 0=1 T1 1=1 T1 2=1
4 线程的调度和控制方法 线程调度注意的问题: • 注意:并不是在所有系统中运行java程序时都采用时间片策略调度线程,所以一个线程在空闲时应该主动放弃cpu,以使其它同优先级(调用yield()方法)和低优先级(调用sleep()方法)的线程得到执行。
4 线程的调度和控制方法 4.3基本的线程控制 • 终止线程 线程执行完其run()方法后,自然终止。 • 测试线程状态 可以通过Thread中的isAlive()方法来获取线程是否处于活动状态; 线程由start()方法启动后,直到其被终止之间的任何时刻,都处于Alive状态。
4 线程的调度和控制方法 • 线程的暂停和恢复 sleep()、join()方法 当前线程等待调用该方法的线程结束后,再恢复执行。 TimerThread tt=new TimerThread(100); tt.start(); …… public void timeout(){ tt.join();//等待线程tt执行完后再继续执行 ……}
4 线程的调度和控制方法 • yield()方法 调用该方法的线程把自己的控制权让出来,线程调度器把该线程放到同一优先级的Runnable队列的最后,然后从该队列中取出下一个线程执行。该方法是给同优先级的线程以执行的机会,如果同 优先级的Runnable队列中没有其他线程,则该线程继续执行。
线程1 取过来 资源 加1后送回去 变量 线程2 线程10 stack push() pop() push() 5 多线程互斥与同步 1. 数据的完整性
5 多线程的互斥与同步 临界资源 public class stack { int idx=0; char[] data=new char[6]; public stack() { } public void push(String c){ if(idx<6){ data[idx]=c; idx++; } } public String pop(){ if(idx>0) { idx--; return data[idx]; }else {return "stack is null"; } } } } 两个线程A和B在同时使用stack的同一个对象,A正在往堆栈里push一个数据,B则要从堆栈中pop一个数据
5 多线程的互斥与同步 • 操作之前data=|p|q| | | | | idx=2 • A执行push中的第一个语句,将r推入堆栈; data=|p|q|r| | | | idx=2 • A还未执行idx++语句,A的执行被B中断,B执行pop方法,返回q: data=|p|q|r| | | | idx=1 • A继续执行push语句的第二个语句: data=|p|q|r | | | | idx=2 最后的结果相当于r没有入栈 产生这种问题的原因在于对共享数据访问的操作的不完整性。
5 多线程的互斥与同步 • 在java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。 1 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象。 2 关键字synchronized来与对象互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
public void push(String c){ synchronized(this){ if(idx<6){ data[idx]=c; idx++; } } } public String pop(){ synchronized(this){ if(idx>0) { idx--; return data[idx]; }else { return "stack is null"; } } }
5 多线程的互斥与同步 • Synchronized除了像上面讲的放在对象前面限制一段代码的执行外,还可以放在方法声明中,表示整个方法为同步方法。 public synchronized void push (char c) { …… } 如果synchronized用在类声明中,则表明该类中的所有方法都是synchronized 的。
线程1 线程2 监 视 器 push pop 5 多线程的互斥与同步 • 用synchronized来标识的区域或方法即为监视器监视的部分。 • 一个类或一个对象由一个监视器,如果一个程序内有两个方法使用synchronized标志,则他们在一个监视器管理之下. • 一般情况下,只在方法的层次上使用关键区
线程2 监视器 线程1 stack 5 多线程的互斥与同步 • 监视器阻止两个线程同时访问同一个条件变量或方法,它如同锁一样作用在数据上. • 线程1进入push方法时,获得监视器(加锁);当线程1的方法执行完毕返回时,释放监视器(开锁),线程2的方能进入pop方法.
. 生产者 消费者 共享对象 write . read 5 多线程的互斥与同步 2. 等待同步数据 • 可能出现的问题: • 生产者比消费者快时,消费者会漏掉一些数据没有取到 • 消费者比生产者快时,消费者取相同的数据. • notify()和wait ()方法用来协调读取的关系. • notify()和wait ()都只能从同步方法中的调用.
多线程的同步 public class stack { private int idx=0; private char[] data=new char[6]; public stack() { } public synchronized void push(char c){ while (idx==data.length) { try{ this.wait(); }catch(InterruptedException e){} } data[idx]=c; System.out.println("Push:"+c); idx++; this.notify();} public synchronized char pop(){ while (idx==0){ try{ this.wait(); }catch(InterruptedException e){} } this.notify(); idx--; return data[idx]; } }
生成者和消费者问题 public class Push extends Thread{ stack s; public Push(stack s) { this.s=s; } public void run() { char c; for (int i = 0; i<6; i++) { c=(char)(Math.random()*26+'A'); s.push(c); //System.out.println("Push:"+c); try{ sleep((int)(Math.random()*1000)); }catch(InterruptedException e){ }} }}
public class Pop extends Thread { stack s; public Pop(stack s) { this.s=s; } public void run() { for (int i = 0; i<6; i++) { char c=s.pop(); System.out.println("pop:"+c); try{ sleep((int)(Math.random()*1000)); }catch(InterruptedException e){ } }} }
public class ThreadTest { public static void main(String[] args) { stack s=new stack(); Thread push1=new Push(s); Thread pop1=new Pop(s); push1.start(); pop1.start(); } } 执行结果: Push:J pop:J Push:K Push:A pop:A Push:R Push:Y Push:T pop:T pop:Y pop:R pop:K
5 多线程的互斥与同步 • notify的作用是唤醒正在等待同一个监视器的线程. • wait的作用是让当前线程等待
5 多线程的互斥与同步 wait(), notify(), notifyAll() (1) wait,noitfy,notifyAll必须在已经持有锁的情况下执行,所以它们只能出现在synchronized作用的范围内。 (2) wait释放已持有的锁,进入wait队列。 (3) notify唤醒wait队列中的第一个线程并把它移入申请队列。 (4) notifyAll唤醒队列中的所有的线程并把它们移入申请队列。
5 多线程的互斥与同步 resume():要求被暂停得线程继续执行 suspend():暂停线程的执行 在JDK1.2中不再使用resume和 suspend,其相应功能由wait()和notify()来实现。
stop()方法: 当一个线程执行完所有语句后就自动终止,调用线程的stop()方法,也可以强制终止线程。 但在JDK1.2中不再使用stop(),而是采用标记来使线程中的run()方法退出。 5 多线程的互斥与同步
5 多线程的互斥与同步 public class Xyz implements Runnable { private boolean timeToQuit=false; public void run() { while (!timeToQuit) {…..} //clean up before run() ends; } public void stopRunning() { timeToQuit=true;} }
5 多线程的互斥与同步 public class ControlThread { private Runnable r=new Xyz(); private Thread t=new Thread(r); public void startThread() { t.start(); } publi void stopThread() { r.stopRunning();} }
6 小结 1. 实现线程有两种方法: • 实现Ruannable接口 • 继承Thread类 2. 当新线程被启动时,java调用该线程的run方 法,它是Thread的核心. 3. 线程由四个状态:创建,运行,暂停,死亡.
6 小结 4. 两个或多个线程竞争资源时,需要用同步的方法协调资源. 5. 多个线程执行时,要用到同步方法,即使用 synchronized的关键字设定同步区 6. wait和notify起协调作用