首页 JAVA专题技术综述之线程篇

JAVA专题技术综述之线程篇

举报
开通vip

JAVA专题技术综述之线程篇 JAVA专题技术综述 之 线程篇 编写具有多线程能力的程序经常会用到的方法有: run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join() 还有一个重要的关键字:synchronized 本文将对以上内容进行讲解。 一:run()和 start() 示例 1: public class ThreadTest extends Thread { public void run() { for(int i=...

JAVA专题技术综述之线程篇
JAVA专题技术综述 之 线程篇 编写具有多线程能力的程序经常会用到的方法有: run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join() 还有一个重要的关键字:synchronized 本文将对以上内容进行讲解。 一:run()和 start() 示例 1: public class ThreadTest extends Thread { public void run() { for(int i=0;i<10;i++) { System.out.print(" " + i); } } public static void main(String[] args) { new ThreadTest().start(); new ThreadTest().start(); } } 这是个简单的多线程程序。run()和 start()是大家都很熟悉的两个方法。把希望并行处理的 代码都放在 run()中;stat()用于自动调用 run(),这是 JAVA的内在机制规定的。并且 run() 的访问控制符必须是 public,返回值必须是 void(这种说法不准确,run()没有返回值),run() 不带参数。 这些规定想必大家都早已知道了,但你是否清楚为什么 run方法必须声明成这样的形式?这涉 及到 JAVA的方法覆盖和重载的规定。这些内容很重要,请读者参考相关资料。 二:关键字 synchronized 有了 synchronized关键字,多线程程序的运行结果将变得可以控制。synchronized关键 字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,JAVA是面向 对象的程序设计语言,所以初学者在编写多线程程序时,容易分不清哪些数据是共享数据。请看下 面的例子: 示例 2: public class ThreadTest implements Runnable { 1 public synchronized void run() { for(int i=0;i<10;i++) { System.out.print(" " + i); } } public static void main(String[] args) { Runnable r1 = new ThreadTest(); Runnable r2 = new ThreadTest(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } 在这个程序中,run()被加上了 synchronized关键字。在 main方法中创建了两个线程。你 可能会认为此程序的运行结果一定为:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。但你 错了!这个程序中 synchronized关键字保护的不是共享数据(其实在这个程序中 synchronized 关键字没有起到任何作用,此程序的运行结果是不可预先确定的)。这个程序中的 t1,t2 是两个对 象(r1,r2)的线程。JAVA 是面向对象的程序设计语言,不同的对象的数据是不同的,r1,r2 有 各自的 run()方法,而 synchronized使同一个对象的多个线程,在某个时刻只有其中的一个线程 可以访问这个对象的 synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程 访问这个对象的某个 synchronized数据时,这个对象的所有被 synchronized修饰的数据将被 上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的 synchronized数据 时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问 synchronized数据。 示例 3: public class ThreadTest implements Runnable { public synchronized void run() { for(int i=0;i<10;i++) { System.out.print(" " + i); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); 2 t2.start(); } } 如果你运行 1000次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的 synchronized保护的是共享数据。t1,t2是同一个对象(r) 的两个线程,当其中的一个线程(例如:t1)开始执行 run()方法时,由于 run()受 synchronized 保护,所以同一个对象的其他线程(t2)无法访问 synchronized 方法(run 方法)。只有当 t1 执行完后 t2才有机会执行。 示例 4: public class ThreadTest implements Runnable { public void run() { synchronized(this) { for(int i=0;i<10;i++) { System.out.print(" " + i); } } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } 这个程序与示例 3的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例 4的形式,this代 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf “这个对象”。没有必要把整个 run()保护起来,run()中的代码只有一个 for 循环,所以只要保护 for循环就可以了。 示例 5: public class ThreadTest implements Runnable { public void run() { for(int k=0;k<5;k++) { System.out.println(Thread.currentThread().getName() + " : for loop : " + k); 3 } synchronized(this) { for(int k=0;k<5;k++) { System.out.println(Thread.currentThread().getName() + " : synchronized for loop : " + k); } } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r,"t1_name"); Thread t2 = new Thread(r,"t2_name"); t1.start(); t2.start(); } } 运行结果: t1_name : for loop : 0 t1_name : for loop : 1 t1_name : for loop : 2 t2_name : for loop : 0 t1_name : for loop : 3 t2_name : for loop : 1 t1_name : for loop : 4 t2_name : for loop : 2 t1_name : synchronized for loop : 0 t2_name : for loop : 3 t1_name : synchronized for loop : 1 t2_name : for loop : 4 t1_name : synchronized for loop : 2 t1_name : synchronized for loop : 3 t1_name : synchronized for loop : 4 t2_name : synchronized for loop : 0 t2_name : synchronized for loop : 1 t2_name : synchronized for loop : 2 t2_name : synchronized for loop : 3 t2_name : synchronized for loop : 4 第一个 for循环没有受 synchronized保护。对于第一个 for循环,t1,t2可以同时访问。 运行结果表明 t1 执行到了 k=2 时,t2 开始执行了。t1 首先执行完了第一个 for 循环,此时 t2 4 还没有执行完第一个 for循环(t2 刚执行到 k=2)。t1开始执行第二个 for循环,当 t1的第二 个 for循环执行到 k=1时,t2的第一个 for循环执行完了。t2想开始执行第二个 for循环,但 由于 t1首先执行了第二个 for循环,这个对象的锁标志自然在 t1手中(synchronized方法的 执行权也就落到了 t1手中),在 t1没执行完第二个 for循环的时候,它是不会释放锁标志的。所 以 t2必须等到 t1执行完第二个 for循环后,它才可以执行第二个 for循环。 三:sleep() 示例 6: public class ThreadTest implements Runnable { public void run() { for(int k=0;k<5;k++) { if(k == 2) { try { Thread.currentThread().sleep(5000); } catch(Exception e) {} } System.out.print(" " + k); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t = new Thread(r); t.start(); } } sleep方法会使当前的线程暂停执行一定时间(给其它线程运行机会)。读者可以运行示例 6, 看看结果就明白了。sleep方法会抛出异常,必须提供捕获代码。 示例 7: public class ThreadTest implements Runnable { public void run() { 5 for(int k=0;k<5;k++) { if(k == 2) { try { Thread.currentThread().sleep(5000); } catch(Exception e) {} } System.out.println(Thread.currentThread().getName() + " : " + k); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r,"t1_name"); Thread t2 = new Thread(r,"t2_name"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); } } t1 被设置了最高的优先级,t2 被设置了最低的优先级。t1 不执行完,t2 就没有机会执行。 但由于 t1在执行的中途休息了 5秒中,这使得 t2就有机会执行了。读者可以运行这个程序试试看。 示例 8: public class ThreadTest implements Runnable { public synchronized void run() { for(int k=0;k<5;k++) { if(k == 2) { try { Thread.currentThread().sleep(5000); } catch(Exception e) {} 6 } System.out.println(Thread.currentThread().getName() + " : " + k); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r,"t1_name"); Thread t2 = new Thread(r,"t2_name"); t1.start(); t2.start(); } } 请读者首先运行示例 8程序,从运行结果上看:一个线程在 sleep的时候,并不会释放这个对 象的锁标志。 四:join() 示例 9: public class ThreadTest implements Runnable { public static int a = 0; public void run() { for(int k=0;k<5;k++) { a = a + 1; } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t = new Thread(r); t.start(); System.out.println(a); } } 请问程序的输出结果是 5吗?答案是:有可能。其实你很难遇到输出 5的时候,通常情况下都 不是 5。这里不讲解为什么输出结果不是 5,我要讲的是:怎样才能让输出结果为 5!其实很简单, join()方法提供了这种功能。join()方法,它能够使调用该方法的线程在此之前执行完毕。 把示例 9的 main()方法该成如下这样: 7 public static void main(String[] args) throws Exception { Runnable r = new ThreadTest(); Thread t = new Thread(r); t.start(); t.join(); System.out.println(a); } 这时,输出结果肯定是 5!join()方法会抛出异常,应该提供捕获代码。或留给 JDK捕获。 示例 10: public class ThreadTest implements Runnable { public void run() { for(int k=0;k<10;k++) { System.out.print(" " + k); } } public static void main(String[] args) throws Exception { Runnable r = new ThreadTest(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t1.join(); t2.start(); } } 运行这个程序,看看结果是否与示例 3一样? 五:yield() yield()方法与 sleep()方法相似,只是它不能由用户指定线程暂停多长时间。按照 SUN 的 说法:sleep方法可以使低优先级的线程得到执行的机会,当然也可以让同优先级和高优先级的线 程有执行的机会。而 yield()方法只能使同优先级的线程有执行的机会。 示例 11: public class ThreadTest implements Runnable { public void run() { 8 for(int k=0;k<10;k++) { if(k == 5 && Thread.currentThread().getName().equals("t1")) { Thread.yield(); } System.out.println(Thread.currentThread().getName() + " : " + k); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r,"t1"); Thread t2 = new Thread(r,"t2"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); } } 输出结果: t1 : 0 t1 : 1 t1 : 2 t1 : 3 t1 : 4 t1 : 5 t1 : 6 t1 : 7 t1 : 8 t1 : 9 t2 : 0 t2 : 1 t2 : 2 t2 : 3 t2 : 4 t2 : 5 t2 : 6 t2 : 7 t2 : 8 t2 : 9 多次运行这个程序,输出也是一样。这说明:yield()方法不会使不同优先级的线程有执行的 9 机会。 六:wait(),notify(),notifyAll() 首先说明:wait(),notify(),notifyAll()这些方法由 java.lang.Object类提供,而上面 讲到的方法都是由 java.lang.Thread类提供(Thread类实现了 Runnable接口)。 wait(),notify(),notifyAll()这三个方法用于协调多个线程对共享数据的存取,所以必 须在 synchronized语句块内使用这三个方法。先看下面了例子: 示例 12: public class ThreadTest implements Runnable { public static int shareVar = 0; public synchronized void run() { if(shareVar == 0) { for(int i=0;i<10;i++) { shareVar++ ; if(shareVar == 5) { try { this.wait(); } catch(Exception e) {} } } } if(shareVar != 0) { System.out.print(Thread.currentThread().getName()); System.out.println(" shareVar = " + shareVar); this.notify(); } } public static void main(String[] args) { Runnable r = new ThreadTest(); Thread t1 = new Thread(r,"t1"); 10 Thread t2 = new Thread(r,"t2"); t1.start(); t2.start(); } } 运行结果: t2 shareVar = 5 t1 shareVar = 10 t1线程最先执行。由于初始状态下 shareVar为 0,t1将使 shareVar连续加 1,当 shareVar 的值为 5时,t1调用 wait()方法,t1将处于休息状态,同时释放锁标志。这时 t2得到了锁标志 开始执行,shareVar的值已经变为 5,所以 t2直接输出 shareVar的值,然后再调用 notify() 方法唤醒 t1。t1 接着上次休息前的进度继续执行,把 shareVar 的值一直加到 10,由于此刻 shareVar的值不为 0,所以 t1将输出此刻 shareVar的值,然后再调用 notify()方法,由于 此刻已经没有等待锁标志的线程,所以此调用语句不起任何作用。 这个程序简单的示范了 wait(),notify()的用法,读者还需要在实践中继续摸索。 七:关于线程的补充 编写一个具有多线程能力的程序可以继承 Thread类,也可以实现 Runnable接口。在这两个 方法中如何选择呢?从面向对象的角度考虑,作者建议你实现 Runnable接口。有时你也必须实现 Runnable接口,例如当你编写具有多线程能力的小应用程序的时候。 线程的调度: 一个 Thread对象在它的生命周期中会处于各种不同的状态,上图形象地说明了这点。 wa in New RunningRunnable Otherwise Blocked Dead Blocked in object`s it()pool Blocked in object`s lock pool Scheduler completes run() start() sleep() or join() sleep() timeout or thread join()s or interupt() terupt() notify() Lock available synchronized() Thread states 11 调用 start()方法使线程处于可运行状态,这意味着它可以由 JVM 调度并执行。这并不意味 着线程就会立即运行。 实际上,程序中的多个线程并不是同时执行的。除非线程正在真正的多 CPU计算机系统上执行, 否则线程使用单 CPU必须轮流执行。但是,由于这发生的很快,我们常常认为这些线程是同时执行 的。 JAVA运行时系统的计划调度程序是抢占性的。如果计划调度程序正在运行一个线程并且来了另 一个优先级更高的线程,那么当前正在执行的线程就被暂时终止而让更高优先级的线程执行。 JAVA计划调度程序不会为与当前线程具有同样优先级的另一个线程去抢占当前的线程。但是, 尽管计划调度程序本身没有时间片(即它没有给相同优先级的线程以执行用的时间片),但以 Thread类为基础的线程的系统实现可能会支持时间片分配。这依赖具体的操作系统,Windows与 UNIX在这个问题上的支持不会完全一样。 由于你不能肯定小应用程序将运行在什么操作系统上,因此你不应该编写出依赖时间片分配的 程序。就是说,应该使用 yield方法以允许相同优先级的线程有机会执行而不是希望每一个线程都 自动得到一段 CPU时间片。 Thread 类提供给你与系统无关的处理线程的机制。但是,线程的实际实现取决于 JAVA 运行 所在的操作系统。因此,线程化的程序确实是利用了支持线程的操作系统。 当创建线程时,可以赋予它优先级。它的优先级越高,它就越能影响运行系统。JAVA运行系统 使用一个负责在所有执行 JAVA 程序内运行所有存在的计划调度程序。该计划调度程序实际上使用 一个固定优先级的算法来保证每个程序中的最高优先级的线程得到 CPU——允许最高优先级的线程 在其它线程之前执行。 对于在一个程序中有几个相同优先级的线程等待执行的情况,该计划调度程序循环地选择它们, 当进行下一次选择时选择前面没有执行的线程,具有相同优先级的所有的线程都受到平等的对待。 较低优先级的线程在较高优先级的线程已经死亡或者进入不可执行状态之后才能执行。 继续讨论 wait(),notify(),notifyAll(): 当线程执行了对一个特定对象的 wait()调用时,那个线程被放到与那个对象相关的等待池中。 此外,调用 wait()的线程自动释放对象的锁标志。 可以调用不同的 wait():wait() 或 wait(long timeout) 对一个特定对象执行 notify()调用时,将从对象的等待池中移走一个任意的线程,并放到锁 标志等待池中,那里的线程一直在等待,直到可以获得对象的锁标志。notifyAll()方法将从对象 等待池中移走所有等待那个对象的线程并放到锁标志等待池中。只有锁标志等待池中的线程能获取 对象的锁标志,锁标志允许线程从上次因调用 wait()而中断的地方开始继续运行。 在许多实现了wait()/notify()机制的系统中,醒来的线程必定是那个等待时间最长的线程。 然而,在 Java技术中,并不保证这点。 注意,不管是否有线程在等待,都可以调用 notify()。如果对一个对象调用 notify()方法, 而在这个对象的锁标志等待池中并没有线程,那么 notify()调用将不起任何作用。 在 JAVA中,多线程是一个神奇的主题。之所以说它“神奇”,是因为多线程程序的运行结果不 可预测,但我们又可以通过某些方法控制多线程程序的执行。要想灵活使用多线程,读者还需要大 量实践。 另外,从 JDK 1.2开始,SUN就不建议使用 resume(),stop(),suspend()了。 本文作者:徐明杰 SCJP/MCSE/MCDBA xmjemail@yahoo.com.cn 2002.9 12
本文档为【JAVA专题技术综述之线程篇】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_886435
暂无简介~
格式:pdf
大小:115KB
软件:PDF阅读器
页数:12
分类:互联网
上传时间:2010-01-18
浏览量:19