Java多线程开发系列之四:玩转多线程(线程的控制2)

   在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join()、守护线程。本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠、让步、优先级、挂起和恢复、停止等。

  废话不多说,我们直接进入正题:



 3、线程睡眠  sleep()

  所有介绍多线程开发的学习案例中,基本都有用到这个方法,这个方法的意思就是睡眠(是真的,请相信我...)。好吧,如果你觉得不够具体,可以认为是让当前线程暂停一下,当前线程随之进入阻塞状态,当睡眠时间结束后,当前线程重新进入就绪状态,开始新一轮的抢占计划!

那么这个方法在实际开发中,有哪些用途呢?我举个例子,很多情况下,当前线程并不需要实时的监控或者是运行,只是会定期的检查一下某个状态是否达标,如果符合出发条件了,那么就做某一件事情,否则继续睡眠。比如心跳模式下,我们会派一个守护线程向服务端发送数据请求,当收到回应时,那么我们会睡眠一段时间,当再次苏醒后,我们继续发送这样的请求。现实生活中的例子,比如我们在等某个电视是否开播,可是又不想看之前的广告,所以我们可能会等一会将电视频道切换到要播放的位置查看一下,如果还在播放广告,那么我们就跳到其他频道观看,然后定期的切换到目标频道进行查看一下。

代码如下:

 1     public class ThreadStudy
 2     {
 3         public static main(String[] arg)throws Exception
 4         {
 5             for(int i=0;i<=1000;i++)
 6             {
 7                 if(IsInternetAccess())
 8                 {
 9                     Thread.sleep(1000*6);//注意这里
10                 }
11                 else
12                 {
13                     System.out.println("Error! Can not Access Internet!")
14                     break;
15                 }
16             }
17         }
18         private Boolean IsInternetAccess()
19         {
20             //bala bala
21             return true;
22         }
23     }

  代码的意思是检查网络是否通畅,如果通畅的话那么进入睡眠,睡眠6秒钟后再次苏醒进行一次检查。通过让线程睡眠,我们可以有效的分配资源,在闲时让其他线程可以更快的拿到cpu资源。这里有一点需要注意的是,线程睡眠后,进入阻塞状态(无论此时cpu是否空闲,都仍然会暂停,是强制性的),当睡眠时间结束,进入的是就绪状态,需要再次竞争才可以抢占到cpu权限,而非睡眠结束后立即可以执行方法。所以实际间隔时间是大于等于睡眠时间的。

java Thread类提供了两个静态方法来暂停线程

1  static void sleep(long millis)
2
3  static void sleep(long millis,int nanos)

  millis为毫秒,nanos为微秒,与线程join()类似,由于jvm和硬件的缘故,我们也基本只用方法1。



4、 线程让步 yield()

在生活中我们都遇到过这样的例子,在公交车、地铁上作一名安静的美男子(或者是女汉子),这时候进来了一位老人、孕妇等,你默默的站起来,将座位让给了老人。自己去旁边候着,等着新的空闲座位。或者是你默默的玩着电脑游戏,然后你妈妈大声的喊你的全名(是的,是全名),这时候你第一反应是,我又做错什么了,第二反应就是放下手上的鼠标,乖乖的跑到你老妈面前接受训斥。所有的这一切都是由于事情的紧急性当前正在处理的线程被搁置起来,我们(cpu)处理当前的紧急事务。在软件开发中,也有类似的场景,比如一条线程处理的任务过大,其他线程始终无法抢占到资源,这时候我们就要主动的进行让步,给其他线程一个公平抢占的机会。

这里附加一份来自网络的图片:在我们强大的时候,我们应该给弱者一个机会。咳咳  回归正题。

下面是代码

 1 public class TestThread extends Thead
 2 {
 3     public testThread(String name)
 4     {
 5         super(name);
 6     }
 7
 8     public void run()
 9     {
10         for(int i=0;i<=1000000;ii++)
11         {
12             send("MsgBody");
13             if(i%100==0)
14             {
15                 Thread.yield();//注意看这里
16             }
17         }
18     }
19
20     public static void main(String[] args) throws Exception
21     {
22         TestThread thread1=new TestThread("thread1");
23         thread1.setPriority(Thread.MAX_PRIORITY);//注意看这里
24
25         TestThread thread2=new TestThread("thread2");
26         thread1.setPriority(Thread.MIN_PRIORITY);//注意看这里
27         thread1.start();
28         thread2.start();
29     }
30 }

我们启动线程后,当线程每发送一百次消息后,我们暂停一次当前线程,使当前线程进入就绪状态。此时CPU会重新计算一次优先级,选择优先级较高者启动。
此处比较一下 sleep方法和yield()方法。

(1)sleep方法 暂停线程后,线程会进入阻塞状态(即使是一瞬间),那么在这一刻cpu只会选择已经做好就绪状态的线程,故不会选择当前正在睡眠的线程。(即使没有其他可用线程)。而yield()方法会使当前线程即刻起进入就绪状态,cpu选择的可选线程范围中,包含当前执行yield()方法的线程。如若没有其他线程的优先级高于(或者等于) yield()的线程,则cpu仍会选择原有yield()的线程重新启动。

(2)sleep方法会抛出 InterruptedException 异常,所以调用sleep方法需要声明或捕捉该异常(比C#处理异常而言是够麻烦的),而yield没有声明抛出异常。

(3)sleep方法的移植性较好,可以对应很多平台的底层方法,所以用sleep()的地方要多余yield()的地方;

(4)sleep 暂停线程后,线程会睡眠 一定时间,然后才会变为就绪状态,倘若定义为sleep(0)后,则阻塞状态的时间为0,即刻进入就绪状态,这种用法与yield()的用法基本上是相同的:即都是让cpu进行一次新的选择,避免由于当前线程过度的霸占cpu,造成程序假死。

这两个方法最大的不同点是 sleep会抛出异常需要处理,yield()不会; 而且两者的微小区别在各个版本的jdk中也不一样,大家看以参阅stackoverflow上的这个问题:Are Thread.sleep(0) and Thread.yield() statements equivalent?(点此进入



5、线程的优先级设定

  线程的优先级相当于是一个机会的权重,优先级高时,获得执行机会的可能性就越大,反之获得执行机会的可能性就越小。(记住只是可能性越大或越小)。

  在本节的线程让步这一部分的代码里我们已经用代码展示了如何设置线程的优先级此处不做特别的代码展示。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

Thread为我们提供了两个方法来分别设置和获取线程的优先级。

  

1 setPriority(int newPriority)
2 getPriority()

setPriority为设置优先级,参数的取值范围是 1~10之前。

同时还设定了三个静态常量:
Tread.MAX_PRIORITY=10;

Tread.NORM_PRIORITY=5;

Tread.MIN_PRIORITY=1;

  尽管java为线程提供了10个优先级,但是底层平台线程的优先级往往并不为10,所以就导致了两者不是意义对应的关系。(比如OS只有五个优先级,这样每两个优先级只对应一个OS的优先级)。 此时我们常常只用这三个静态常量来设置优先级,而不是详细的指明具体的优先级值(因为可能多个优先级对应OS的某一个优先级),造成不必要的麻烦。

  另外每个线程默认的优先级都与创建他的父进程的优先级相同,在默认情况下Main线程优先级为普通,所以上述代码创建的新线程默认也为普通优先级。

  下面是优先级概念的重点:

  其实你设置的优先级并不能真正代表该线程的或者启动的优先级,这只是OS启动线程时计算优先级的一个参考指标。OS还会查看当前线程是否长时间的霸占cpu,如果是这样的话,OS会适度的调高对其它“饥饿”线程的优先级。对于那些长期霸占cpu的线程进行强制的挂起。进行这种设置只是能在某种程度上增加该线程被执行的机会。其实那些长期霸占cpu的线程也并非单次霸占的时间长,而是被连续选中的情况非常多,造成一种长期霸占的假象。

  所以设置优先级后,线程真正执行的顺序并不可以预测甚至可以说是有点混乱的。在明白了这点以后,我们在开发控制多线程,并不能完全的寄希望于通过简单的设置优先级来安排线程的执行顺序。

此处参考了两篇文章,更多详情请参考原文:

(1)Java多线程 -- 线程的优先级(原文链接

(2)Thread.sleep(0)的意义(原文链接)



6、强制结束线程Stop()

有时我们会发现有些正在运行的线程,已经没有必要继续执行下去了,但是距离多线程结束还有一段时间,这时我们就需要强制结束多线程。java曾经提供过一个专门用于结束线程的方法Stop(),但是这个方法现在已经被废弃掉了,并不推荐开发者使用。

  这是由于这个方法具有固有的不安全性。用Thread.stop 来结束线程,jvm会强制释放它锁定的所有对象。当某一时刻对象的状态并不一致时(正在处理事务的过程中),如果强制释放掉对象,则可能会导致很多意想不到的后果。说的具体一点就是:系统会以被锁定资源的栈顶产生一个ThreadDeath异常。这个unchecked Exception 会默默的关闭掉相关的线程。此时对象内部的数据可能会不一致,而用户并不会收到任何对象不一致的报警。这个不一致的后果只会在未来使用过程中才会被发现,此时已经造成了无法预料的后果。

  有些人可能会考虑通过调用Stop方法,然后再捕捉ThreadDeath的形式,避免这种形式。这种想法看似可以实现,其实由于ThreadDeath这个异常可能在任何位置抛出,需要及细致的考虑。而且即使考虑到了,在捕捉处理该异常时,系统可能又会抛出新的ThreadDeath。所以我们应该在源头上就扼杀掉这种方式,而不是通过不断的打补丁来修复。

那么问题来了,如果我们真的要关闭掉某个线程,应该怎么处理呢?

通过Stop方法的讲解我们可以明白,在线程的外部来关闭线程往往很难处理好数据一致性、以及线程内部运行过程的问题。那么我们可以通过设定一直标志变量,然后线程定期的检查这个变量是否为结束标识来确定是否继续运行。

例如笔者曾经写过一个监控计算机指标的线程。这个线程会定期的检查缓存中的状态变量。这个状态缓存是外部可以设定的。当线程发现此变量已经被设定为“结束”时,则会在内部处理好剩余工作,直接运行完Run方法。



7、线程的挂起和恢复 suspend()和resume()

我们有时需要对线程进行挂起,而具体挂起的时间并不清楚,只可能在未来某个条件下,通知这个线程可以开始工作了。java为我们专门提供了这样的两个方法:

挂起 suspend()/恢复resume。

通过标题我们已经知道这两个方法也同样不被java所推荐,但是为什么会这样呢?

suspend是直接挂起当前线程,使其进入阻塞状态,而对他内部控制和锁定的资源并不进行修改(这与stop方法类似,线程外部往往很难查看内部运行的状态和控制的资源,所以也就很难处理)。这样这个被挂起的线程所锁定的资源就再也不能被其他资源所访问,造成了一种假死锁的状态。只有当线程被恢复(resume)后,并且释放掉手里的资源,其他线程才可以重新访问资源,但是倘若其他线程在恢复(resume)被挂起(suspend)的线程直线,需要先访问被锁定的资源,此时就会形成真正的锁定。

那么问题来了,如果我们真的要挂起某个线程,应该怎么处理呢?

  这个与stop()同理,我们可以在可能被挂起的线程内部设置一个标识,指出这个线程当前是否要被挂起,若变量指示要挂起,则使用wait()命令让其进入等待状态,若标识指出可以恢复线程时,则用notify()重新唤醒这个线程。(这两个方法我会在后文的线程通信中讲解)。

此处参考了两篇文章,更多详情请参考原文:

(1)为何不赞成使用Thread.stopsuspend和resume()(原文链接

  (2)JAVA STOP方法的不安全性(原文链接

时间: 2024-09-30 22:55:49

Java多线程开发系列之四:玩转多线程(线程的控制2)的相关文章

Java多线程开发系列之一:走进多线程

对编程语言的基础知识:分支.选择.循环.面向对象等基本概念后,我们需要对java高级编程有一定的学习,这里不可避免的要接触到多线程开发. 由于多线程开发整体的系统比较大,我会写一个系列的文章总结介绍 多线程开发的概念.使用.线程状态.同步.线程池.希望与大家共勉. 在第一部分,也就是本节我们先介绍下 什么是多线程程序.线程和进程又是什么,以及为什么要搞多线程. (一)什么是多线程程序 多线程听上去是非常专业的概念,其实非常简单,我们在日常生活中,经常的接触到多线程. 比如 (1)在工厂,工人努力

Java多线程开发系列之四:玩转多线程(线程的控制1)

在前文中我们已经学习了:线程的基本情况.如何创建多线程.线程的生命周期.利用已有知识我们已经可以写出如何利用多线程处理大量任务这样简单的程序.但是当应用场景复杂时,我们还需要从管理控制入手,更好的操纵多线程.在第一节中我们讲过,使用多线程的好处之一就是我们可以通过编码和已有类库更好的管理和控制多线程.接下来我会详细的介绍如何管理多线程,包括:对线程的等待.守护线程.线程的睡眠.线程的突然停止.线程的让步.线程的优先级等.由于内容比较多,本节先介绍前两部分:对线程的等待.守护线程 1.线程的等待

Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread

事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一.事件派发线程的前世今生 事件(Event)派发(Dispatch)线程(Thread)简写为EDT,也就是各个首字母的简写.在一些书或者博客里边也将其译为事件分发线程.事件调度线程.巴拉巴拉,总之,知道这些名字就行.笔者认为这里翻译成派发更准确点. 熟悉Swing和awt编程的小伙伴对事件派发线程

iOS开发系列之四 - UITextView 用法小结

// 初始化输入框并设置位置和大小 UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 10, 300, 180)]; // 设置预设文本 textView.text = @""; // 设置文本字体 textView.font = [UIFont fontWithName:@"Arial" size:16.5f]; // 设置文本颜色 textView.textColor

iOS多线程开发——GCD的使用与多线程开发浅析(二)

对于iOS多线程开发,我们时刻处于学习之中,在看书中,看文档中,项目开发中,都可以去提高自己.最近刚看完了<Objective-C高级编程 iOS与OS X多线程和内存管理>这本书后,对多线程有了更为深入的理解,故在此做一个总结与记录.这本书我已经上传至网盘  https://pan.baidu.com/s/1c2fX3EC ,这本书是iOS开发者必读的书之一,写得很不错,欢迎大家下载阅读.书的封面如下,故也称狮子书: . (1)多线程会遇到的问题 . 多线程会出现什么问题呢?当多个线程对同一

Java后端开发从初学者玩成大牛的学习路线

如果你是在校学生,务必要在学好基础(比如计算机系统.算法.编译原理等等)的前提下,再考虑去进行下面的学习.第一部分:对于尚未做过Java工作的同学,包括一些在校生以及刚准备转行Java的同学. 一.Java基础首先去找一个Java的基础教程学一下,这里可以推荐一个地址,或者你也可以参照这个地址上去找相应的视频.学习Java基础的时候,应该尽量多动手,很多时候,你想当然的事情,等你写出来运行一下,你就会发现不是这么回事儿,不信你就试试.学完以上内容以后,你应该对Java有一个基本的了解了,你可以用

Java多线程开发系列之二:如何创建多线程

前文已介绍过多线程的基本知识了,比如什么是多线程,什么又是进程,为什么要使用多线程等等. 在了解了软件开发中使用多线程的基本常识后,我们今天来聊聊如何简单的使用多线程. 在Java中创建多线程的方式有两种: (1)写一个子类,这个类要继承自Thread类,于此同时这个子类必须要重写Thread类中的run方法(原因我后文中会提到),然后我们就可以用这个类来创建出一个多线程. (2)仍然是写一个类,这个类要实现Runnable接口,与(1)相同,在这个实现类中也需要重写run方法. 这里有一点要注

Java多线程开发系列之三:线程这一辈子(线程的生命周期)

前文中已经提到了,关于多线程的基础知识和多线程的创建.但是如果想要很好的管理多线程,一定要对线程的生命周期有一个整体概念.本节即对线程的一生进行介绍,让大家对线程的各个时段的状态有一定了解. 线程的一生的状态过程 如下图: 线程会由出生 到运行  再到 死亡.在前文中曾经讲到过(寻找前文请点这里):java中各个线程是抢占式的:cpu一般不会为一个线程从出生一直服务到老,各个线程总是争抢的希望得到cpu的“青睐”.当某个线程发生阻塞时,那么cpu就会被其他线程迅速抢占.而当前阻塞的线程只能变为就

SuperMap iObject入门开发系列之四管线长度统计

本文是一位好友“托马斯”授权给我来发表的,介绍都是他的研究成果,在此,非常感谢. 上一期文章主要写了管线系统的标注功能,结合代码简单讲解了一些超图.NET开发框架气泡Bubble的使用方法,这期的文章介绍一下管线长度统计功能,效果如下图: 功能介绍:通过指定的管线图层获取不同的管线类型,针对不同类型对其进行长度统计,统计可以设置最大最小范围,并提供导出excel表格功能.功能内容并不复杂,主要是要对管线数据进行规范整理,统一数据标准,首先必不可少的是一个分类的字段,在本票的代码里对应的数据字段为