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

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


 3、线程睡眠  sleep()

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

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

代码如下:

     public class ThreadStudy
{
public static main(String[] arg)throws Exception
{
for(int i=0;i<=1000;i++)
{
if(IsInternetAccess())
{
Thread.sleep(1000*6);//注意这里
}
else
{
System.out.println("Error! Can not Access Internet!")
break;
}
}
}
private Boolean IsInternetAccess()
{
//bala bala
return true;
}
}

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

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

  static void sleep(long millis)  

  static void sleep(long millis,int nanos)

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


4、 线程让步 yield()

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

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

下面是代码

 public class TestThread extends Thead
{
public testThread(String name)
{
super(name);
} public void run()
{
for(int i=0;i<=1000000;ii++)
{
send("MsgBody");
if(i%100==0)
{
Thread.yield();//注意看这里
}
}
} public static void main(String[] args) throws Exception
{
TestThread thread1=new TestThread("thread1");
thread1.setPriority(Thread.MAX_PRIORITY);//注意看这里 TestThread thread2=new TestThread("thread2");
thread1.setPriority(Thread.MIN_PRIORITY);//注意看这里
thread1.start();
thread2.start();
}
}

我们启动线程后,当线程每发送一百次消息后,我们暂停一次当前线程,使当前线程进入就绪状态。此时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为我们提供了两个方法来分别设置和获取线程的优先级。

  

 setPriority(int newPriority)
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方法的不安全性(原文链接

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

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

    对编程语言的基础知识:分支.选择.循环.面向对象等基本概念理解后,我们需要对java高级编程有一定的学习,这里不可避免的要接触到多线程开发. 由于多线程开发整体的系统比较大,我会写一个系列的文章总结介 ...

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

    在前文中我们已经学习了:线程的基本情况.如何创建多线程.线程的生命周期.利用已有知识我们已经可以写出如何利用多线程处理大量任务这样简单的程序.但是当应用场景复杂时,我们还需要从管理控制入手,更好的操纵 ...

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

    事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一 ...

  4. Java SE开发系列-JDK下载安装

    JDK下载安装 JDK是Java的开发环境,目前JDK内部也包含了JRE,JRE主要是JAVA程序的运行环境. 点击官方下载地址,按着下图操作即可下载对应系统的不同版本JDK. 进入页面滑到页面底部点 ...

  5. java高并发系列 - 第9天:用户线程和守护线程

    守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程.JIT线程都是守护线程.与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作.如果 ...

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

    前文已介绍过多线程的基本知识了,比如什么是多线程,什么又是进程,为什么要使用多线程等等. 在了解了软件开发中使用多线程的基本常识后,我们今天来聊聊如何简单的使用多线程. 在Java中创建多线程的方式有 ...

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

    前文中已经提到了,关于多线程的基础知识和多线程的创建.但是如果想要很好的管理多线程,一定要对线程的生命周期有一个整体概念.本节即对线程的一生进行介绍,让大家对线程的各个时段的状态有一定了解. 线程的一 ...

  8. Java多线程开发系列之五:Springboot 中异步请求方法的使用

    Springboot 中异步线程的使用在过往的后台开发中,我们往往使用java自带的线程或线程池,来进行异步的调用.这对于效果来说没什么,甚至可以让开发人员对底层的状况更清晰,但是对于代码的易读性和可 ...

  9. Windows多线程同步系列之四-----信号量

    信号量说实话自己没怎么使用过.书上大概这样说,信号量设置一个资源访问计数.当该计数值大于0的时候,该信号量对象 为有信号状态,当该计数值等于0的时候,该信号量对象为无信号状态. 我们来查几个主要的AP ...

随机推荐

  1. React Native开发之npm start加速

    在Windows下好不容易安装好React Native环境之后,运行npm start,结果就是无限被等待,快的话160秒(将近3分钟啊....) 而Mac下因为有watchman所以是飞一样的速度 ...

  2. aps.net cored 新概念

    Tag Helpers The EnvironmentTagHelper can be used to include different scripts in your views (for exa ...

  3. WebCrawler

    WebCrawler WebCrawler is a metasearch engine that blends the top search results from Google Search a ...

  4. --自动创建备份SQL

    --自动创建备份SQL DECLARE @dbname VARCHAR(50) ,--要备份的数据库名称 @bakname VARCHAR(50) ,--备份后的bat名称 @sql VARCHAR( ...

  5. [IOS]swift自定义uicollectionviewcell

    刚刚接触swift以及ios,不是很理解有的逻辑,导致某些问题.这里分享一下swift自定义uicollectionviewcell 首先我的viewcontroller不是直接继承uicollect ...

  6. <四>JDBC_PreparedStatement的使用

    WHY? <1>使用Statement需要进行拼写SQL语句,容易出错; <2>PreparedStatement:是Statement的子接口,可以传入带占位符的SQL语句, ...

  7. 随机数是骗人的,.Net、Java、C为我作证(转)

    几乎所有编程语言中都提供了"生成一个随机数"的方法,也就是调用这个方法会生成一个数,我们事先也不知道它生成什么数.比如在.Net中编写下面的代码: Random rand = ne ...

  8. JavaScript:数组大全

    栈/队列 数组es3: pop删除最后一项(栈) shift删除第一项(队列) push增加到最后(栈) unshift增加到最前(队列) reverse翻转 join转字符串 slice截取(切片) ...

  9. PostGr-SQL 基本概念

    http://wenjiesu.iteye.com/blog/801129 [什么是schema?] 究竟什么是schema?这个问题困扰了我很久. 我们只讨论数据库中的schema,而不讨论XML中 ...

  10. 《实时控制软件设计》第一周作业 欧梓峰 U201317662 (更新)

    CNC 插补计算程序分析 前言:插补(Interpolation),即机床数控系统依照一定方法确定刀具运动轨迹的过程.一般是已知起点坐标.终点坐标和轨迹,由数控插补计算程序实时的算出各个中间的坐标来拟 ...