您现在的位置:首页 > >

ITjob就业培训java教材11

发布时间:

第十一章:线程

ITjob 就业培训

第十一章 线程
学习目标
线程的概念 线程状态和调度 线程中断/恢复的几种方式 创建线程的两种方式 线程的控制 线程的同步 实例分析

174

第十一章:线程

ITjob 就业培训

1.线程的概念 .
一个关于计算机的简化的视图是:它有一个执行计算的处理机,包含处理机所执行的程 序的 ROM(只读存储器,在 JAVA 中也叫堆栈),包含程序所要操作的数据的 RAM(随机 存储器,在 JAVA 中也叫堆).在这个简化视图中,只能执行一个作业.一个关于最现代 计算机比较完整的视图允许计算机在同时执行一个以上的作业. 你不需关心这一点是如何实现的,只需从编程的角度考虑就可以了.如果你要执行一个 以上的作业,这类似有一台以上的计算机.在这个模型中,线程(或执行上下文) ,被认 为是带有自己的程序代码和数据的虚拟处理机的封装.java.lang.Thread 类允许用户 创建并控制他们的线程.在单 CPU 的情况下,一个时刻只能运行一个进程,那么进程 在运行时,也只能运行一个线程来代表该进程的执行. 进程是正在执行的程序.一个或更多的线程构成了一个进程(操作系统是以进程为单位 的,而进程是以线程为单位的,进程中必须有一个主线程) .一个线程(执行上下文)由 三个主要部分组成: 一个虚拟 CPU CPU 执行的代码 代码操作的数据 如图所示

代码可以由多个线程共享, 它不依赖数据. 如果两个线程执行同一个类的实例的代码时, 则它们可以共享相同的代码. 类似地,数据可以由多个线程共享,而不依赖代码.如果两个线程共享对一个公共对象 的访问,则它们可以共享相同的数据. 在 Java 编程中,虚拟处理机封装在 Thread 类的一个实例里.构造线程时,定义其上 下文的代码和数据是由传递给它的构造函数的对象指定的. Java 线程分守护线程和用户 线程,由创建时设置.

175

第十一章: 线程

ITjob 就业培训

线程状态和调度
在 Java 中,线程的调度是基于时间片基础上的优先级优先原则 . 抢占式调度模型(优先级优先)是指可能有多个线程是可运行的,但只有一个线程在实际 运行.这个线程会一直运行,直至它不再是可运行的(运行时间到,时间片原则,或者, 另一个具有更高优先级的线程抢占,优先级优先原则).对于后面一种情形,低优先级线 程被高优先级线程抢占了运行的机会. 线程的代码可能执行了一个 Thread.sleep()调用,要求这个线程暂停一段固定的时间. 这个线程可能在等待访问某个资源,而且在这个资源可访问之前,这个线程无法继续运 行. 所有可运行线程根据优先级保存在池中.当一个被阻塞的线程变成可运行时,它会被放 回相应的可运行池.优先级最高的非空池中的线程会得到处理机时间(被运行). 一个 Thread 对象在它的生命周期中会处于各种不同的状态. 下图形象地说明了这点:

线程进入"可运行"状态,并不意味着它立即开始运行.在一个只有一个 CPU 的机器上, 在一个时刻只能进行一个动作. (下节将描述: 如果有一个以上可运行线程时, 系统如何 分配 CPU. ) 因为 Java 线程是抢占式的,所以你必须确保你的代码中的线程会不时地给其它线程运 行的机会.这可以通过在各种时间间隔中发出 sleep()调用来做到.

class ThreadA implements Runnable { public void run() { while (true) { //线程的执行代码部分 try { //给其他的线程提供机会运行 Thread.sleep(7); } catch (Exception e) { } } }
176

第十一章:线程

ITjob 就业培训

}
try 和 catch 块的使用. Thread.sleep()和其它使线程暂停一段时间的方法是可中断的. 线 程 可 以 调 用 另 外 一 个 线 程 的 interrupt() 方 法 , 这 将 向 暂 停 的 线 程 发 出 一 个 InterruptedException. Thread 类的 sleep()方法对当前线程操作,因此被称作 Thread.sleep(x),它是一个 静态方法.sleep()的参数指定以毫秒为单位的线程最小休眠时间.除非线程因为中断 而提早恢复执行,否则它不会在这段时间之前恢复执行.使用该方法只是使当前线程中 断多少毫秒,并不是创建多线程. 例如:

class TestTS {//Thread sleep public static void main(String[] args) { try { Thread.sleep(5000);//中断当前线程(main)5秒,并没有 创建新的线程. } catch (Exception e) { e.printStackTrace(); } System.out.println("Hello World!"); } }

177

第十一章: 线程

ITjob 就业培训

线程中断/恢复的几种方式 线程中断 恢复的几种方式
一个线程可能因为各种原因而不再是可运行的. 该线程调用 Thread.sleep() 进入中断状态必须经过规定的毫秒数才能从中断 状态进入可运行状态 该线程进行了 IO 操作 而进入中断状态必须等待 IO 操作完成, 才能 进入可运 行状态 该线程调用了其它线程的 join()方法,而使自己进入中断状态必须等待调用的 线程执行完,才能进入可运行状态 该线程试图访问被另一个线程锁住的对象 而进入中断状态必须等待另一个线 程释放对象锁, 该线程才能进入可运行状态该线程调用 wait()方法而进入中断 状态必须通过其他线程调用 notify()或者 notifyAll()方法才能进入可运行状态

178

第十一章:线程

ITjob 就业培训

创建线程的两种方式(实现接口的方式请看实例分析 ) 创建线程的两种方式(实现接口的方式请看实例分析 5)
实现 Runnable 接口 继承 Thread 类

实现 Runnable 的优点
从面向对象的角度来看,Thread 类是一个虚拟处理机的严格封装,因此只有当处理机 模型修改或扩展时,才应该继承类. 由于 Java 技术只允许单一继承, 所以如果你已经继承了 Thread, 你就不能再继承其它 任何类.

继承 Thread 的优点
当一个 run()方法体出现在继承 Thread 的类中,用 this 指向实际控制运行的 Thread 实例.因此,代码不再需要使用如下控制: Thread.currentThread().join();而可以简单地用:join(); 因为代码简单了一些,许多 Java 编程语言的程序员使用扩展 Thread 的机制.

179

第十一章: 线程

ITjob 就业培训

线程的控制 终止一个线程: 终止一个线程:
当一个线程结束运行并终止时,它就不能再运行了.可以用一个标志来指示 run()方法, 必须退出一个线程.

public class Runner implements Runnable { private boolean timeToQuit = false; //终止标志 public void run() { while(! timeToQuit) { //当结束条件为假时运行 ... } } //停止运行的方法 public void stopRunning() { timeToQuit = true; } } //控制线程类 public class ControlThread { private Runnable r = new Runner(); private Thread t = new Thread(r); public void startThread() { t.start(); } public void stopThread() { r.stopRunning(); } } 线程的优先级
使用 getPriority 方法测定线程的当前优先级. 使用 setPriority 方法设定线程的当前优 先级.线程优先级是一个整数(1 到 10) .Thread 类包含下列常数: Thread.MIN_PRIORITY 1 (最低级别) Thread.NORM_PRIORITY 5 (默认的级别) Thread.MAX_PRIORITY 10 (最高级别)

延迟线程
sleep()方法是使线程停止一段时间的方法.在 sleep 时间间隔期满后,线程不一定立 即恢复执行. 这是因为在那个时刻, 其它线程可能正在运行而且没有被调度为放弃执行, 除非 (a)"醒来"的线程具有更高的优先级 (b)正在运行的线程因为其它原因而阻塞

180

第十一章:线程

ITjob 就业培训

线程同步
为了保证共享数据在任何线程使用它完成某一特定任务之前是一致的, Java 使用关键字 synchronized,允许程序员控制共享数据的线程.

对象锁
在 Java 技术中,每个对象都有一个和它相关联的标志.这个标志可以被认为是"锁标志 ". synchronized 关键字使线程能和这个标志的交互,即允许独占地存取对象. 当线程运行到 synchronized 语句,它检查作为参数传递的对象,并在继续执行之前试 图从对象获得锁标志. 持有锁标志的线程执行到 synchronized()代码块末尾时将释放锁.即使出现中断或异 常而使得执行流跳出 synchronized()代码块,锁也会自动返回.此外,如果一个线程 对同一个对象两次发出 synchronized 调用,则在跳出最外层的块时,标志会正确地释 放,而最内层的将被忽略.

关键字 synchronized public void push(char c) { synchronized(this) {// synchronized语句块 . . . } } // synchronized方法 public synchronized void push(char c) { . . . } 死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时, 就会发生死锁. 避免死锁的一个通用的经验法则是:决定获取锁的次序并始终遵照这个次序.按照与获 取相反的次序释放锁.

181

第十一章: 线程

ITjob 就业培训

实例分析
例 1: 创建四个线程对同一个数据操作,其中两个线程对该数据执行加 1 操作,两个 线程对该数据减 1 操作 创建数据类

//数据类 class Data { private int k; public void add() { k++; } public void sub() { k--; } public int getK() { return k; } }
创建加数据的线程

//加数据的线程 class ThreadAdd extends Thread { //线程操作的数据 Data data; public ThreadAdd(Data data, String name) { //给当前线程命名 super(name); this.data = data; } //线程执行时所调用的方法 public void run() { for (int i = 0; i < 20; i++) { data.add(); //打印出哪个线程执行的加操作 System.out.println(Thread.currentThread().getName() + " + data.getK()); //每循环一次,让该线程中断5毫秒 try { Thread.sleep(5); } catch (Exception e) { e.printStackTrace(); } } }

"

};

182

第十一章:线程

ITjob 就业培训

创建减数据的线程

//减数据的线程 class ThreadSub extends Thread { //线程所操作的关键数据 Data data; public ThreadSub(Data data, String name) { //给当前线程命名 super(name); this.data = data; } //线程执行时所调用的方法,即线程所执行的代码 public void run() { for (int i = 0; i < 20; i++) { data.sub(); //打印出哪个线程执行的加操作 System.out.println(Thread.currentThread().getName() + " + data.getK()); //每循环一次,让该线程中断5毫秒 try { Thread.sleep(5); } catch (Exception e) { e.printStackTrace(); } } }
启动四个线程运行

"

}; class TestThread { public static void Data data = new //创建四个线程 Thread thadd1 = Thread thadd2 = Thread thsub1 = Thread thsub2 = //启动四个线程 thadd1.start(); thadd2.start(); thsub1.start(); thsub2.start(); } }

main(String[] args) { Data(); new new new new ThreadAdd(data, ThreadAdd(data, ThreadSub(data, ThreadSub(data, "thadd1"); "thadd2"); "thsub1"); "thsub2");

编译,运行以及输出的结果为:

183

第十一章: 线程

ITjob 就业培训

例 2 通过 join()方法中断一个线程 需要修改上例的代码(只修改 main()方法) :

class TestThread { public static void main(String[] args) { Data data = new Data(); //创建四个线程 Thread thadd1 = new ThreadAdd(data, "thadd1"); Thread thadd2 = new ThreadAdd(data, "thadd2"); Thread thsub1 = new ThreadSub(data, "thsub1"); Thread thsub2 = new ThreadSub(data, "thsub2"); //启动四个线程 thadd1.start(); try { thadd1.join(); //thadd1执行完后才输出"join() 已经执 行完毕" } catch (Exception e) { e.printStackTrace(); } System.out.println("join() 已经执行完毕"); thadd2.start(); thsub1.start(); thsub2.start(); } }
运行的结果如图 4:

184

第十一章:线程

ITjob 就业培训

例3 通过 IO 中断线程 class TestThread { public static void main(String[] args) { Data data = new Data(); //创建四个线程 Thread thadd1 = new ThreadAdd(data, "thadd1"); Thread thadd2 = new ThreadAdd(data, "thadd2"); Thread thsub1 = new ThreadSub(data, "thsub1"); Thread thsub2 = new ThreadSub(data, "thsub2"); //启动四个线程 thadd1.start(); try { //等待用户从控制台输入数据 int k = System.in.read(); } catch (Exception e) { e.printStackTrace(); } System.out.println("io 已经执行完毕"); thadd2.start(); thsub1.start(); thsub2.start(); } }
运行的结果如图 5:

185

第十一章: 线程

ITjob 就业培训

例4 线程的同步 先看线程不同步的情况: 先看线程不同步的情况: //数据类
package com.itjob; public class Person { private String name = "王非"; private String sex = "女"; public void put(String name, String sex) { this.name = name; this.sex = sex; } public void get() { System.out.println(name + "----->" + sex); } } //用来显示Person数据 package com.itjob; public class Consumer implements Runnable { Person person; public Consumer(Person person) { this.person = person; } public void run() { while(true) { person.get(); } } }

186

第十一章:线程

ITjob 就业培训

// 用来修改Person数据 package com.itjob; public class Producer implements Runnable { Person person;

public Producer(Person person) { this.person = person; } public void run() { int i = 0; while(true) { if(i==0) { person.put("刘祥", "男"); }else{ person.put("王非", "女"); } i = (i+1)%2; } } } //主程序类 package com.itjob; public class ThreadCom { public static void main(String[] args) { Person person = new Person(); new Thread(new Producer(person)).start(); new Thread(new Consumer(person)).start(); } } 运行的结果如图:

187

第十一章: 线程

ITjob 就业培训

修改为线程同步
只需要修改 Person 类就 OK 了,同步只是对数据加锁,与线程无关,当多个线程访问 同一个数据的时候,就要对数据加锁(同步) . 修改后的代码如:

//数据类
package com.itjob; public class Person { private String name = "王非"; private String sex = "女"; public synchronized void put(String name, String sex) { this.name = name; this.sex = sex; } public synchronized void get() { System.out.println(name + "----->" + sex); } } 运行的结果不会有男女不分的情况!

例 5: 通过 wait()和 notify()/notifyAll()方法进行同步 Object 类中提供了用于线程通信的方法:wait()和 notify(),notifyAll().如果线程对 一个指定的对象 x 发出一个 wait()调用,该线程会暂停执行,此外,调用 wait()的线程 自动释放对象的锁,直到另一个线程对同一个指定的对象 x 发出一个 notify()调用. 为了让线程对一个对象调用 wait()或 notify(),线程必须锁定那个特定的对象.也就是 说,只能在它们被调用的实例的同步块内使用 wait()和 notify(). 根据以上内容,修改 Person 类如下: package com.itjob; public class Person { private String name = "王非";
188

第十一章:线程

ITjob 就业培训

private String sex = "女"; //为true时,修改数据;为false时,显示数据 boolean flag = false; public synchronized void put(String name, String sex) { if(flag) { try { wait(); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } this.name = name; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.sex = sex; flag = true; notifyAll(); } public synchronized void get() { if(!flag) { try { wait(); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } System.out.println(name + "----->" + sex); flag = false; notifyAll(); } }

内容总结
线程与进程的区别 产生线程的两种方式 线程的生命周期和状态 线程同步方法和同步块 多线程同步机制

189

第十一章: 线程

ITjob 就业培训

独立实践 1,创建一个可以容纳 10 个整数类型的数组,数据的加入在数组尾部,删除在头部,
并保证线程操作的安全性. 2,主线程创建两个子线程,一个线程每次往缓冲区里写入一个整数,另一个线程每次 从缓冲区里读出一个整数. 要确保当前缓冲区无数据时不能读, 并且读写不能同时进行. 3,创建两个线程,线程 A 和线程 B,各线程运行打印数据要求(0-100) ,要求,线程 A 必须等待线程 B 完成后,才能再进行打印动作. 4,有三个线程,stu1,stu2,teacher,其中 stu1 准备休眠 10 分钟后运行,而 stu2 准备休眠 1 小时后再运行.Teacher 发出命令"上课" ,叫醒 stu1 线程,stu1 线程醒 来后,stu1 线程再叫醒 stu2 线程.通过程序返应出来. 5,编写一个应用程序,有两个线程,一个负责模仿垂直上抛运动,另一个模仿 45 度的 抛物运动.

190



热文推荐
猜你喜欢
友情链接: 工作计划 总结汇报 团党工作范文 工作范文 表格模版 生活休闲