JAVA 多线程 及线程的 一些理解

来源:互联网 发布:自定义表情制作软件 编辑:程序博客网 时间:2024/05/17 08:45

       对于 多线程的并发 及并发处理: 

 线程场景
 

这幅图中节点代表一个 single Thread,边代表执行的步骤。

模型的描述

如何来描述图 1 中所示的场景呢?可以采用 XML 的格式来描述我们的模型。我定义一个“Thread” element 来表示线程。

<ThreadList><Thread ID = "thread-id" PRETHREAD = "prethread1, prethread2…"></Thread><Thread ID = "thread-id" PRETHREAD = "prethread3, prethread4…"></Thread></ThreadList>

其中 ID 是线程的唯一标识符,PRETHREAD 便是该线程的直接先决线程的ID,每个线程 ID 之间用逗号隔开。

在 Thread 这个 element 里面可以加入你想要该线程执行任务的具体信息。

实际上模型的描述是解决问题非常重要的一个环节,整个线程场景可以用一种一致的形式来描述,作为 Java 多线程并发控制框架引擎的输入。也就是将线程运行的模式用 XML 来描述出来,这样只用改动 XML 配置文件就可以更改整个线程运行的模式,不用改动任何的源代码。

两种实现机制

对于 Java 多线程的运行框架来说,我们将采用“外”和“内”的两种模式来实现。

“外” - 主线程轮询


图 2. 静态类图


 

Thread 是工作线程。ThreadEntry 是 Thread 的包装类,prerequisite 是一个 HashMap,它含有 Thread 的先决线程的状态。如图1中显示的那样,T4 的先决线程是 T2 和 T3,那么 prerequisite 中就包含 T2 和 T3 的状态。TestScenario 中的 threadEntryList 中包含所有的 ThreadEntry。


图 3. 线程执行场景
 

TestScenario 作为主线程,作为一个“外”在的监控者,不断地轮询 threadEntryList 中所有 ThreadEntry 的状态,当 ThreadEntry 接受到 isReady 的查询后查询自己的 prerequisite,当其中所有的先决线程的状态为“正常结束时”,它便返回 ready,那么 TestScenario 便会调用 ThreadEntry 的 startThread() 方法授权该 ThreadEntry 运行线程,Thread 便通过 run() 方法来真正执行线程。并在正常执行完毕后调用 setPreRequisteState() 方法来更新整个 Scenario,threadEntryList 中所有 ThreadEntry 中 prerequisite 里面含有该 Thread 的状态信息为“正常结束”。


图 4. 状态更改的过程
 

如图 1 中所示的 T4 的先决线程为 T2 和 T3,T2 和 T3 并行执行。如图 4 所示,假设 T2 先执行完毕,它会调用 setPreRequisteState() 方法来更新整个 Scenario, threadEntryList 中所有 ThreadEntry 中 prerequisite 里面含有该 T2 的状态信息为“正常结束”。此时,T4 的 prerequisite 中 T2 的状态为“正常结束”,但是 T3 还没有执行完毕,所以其状态为“未完毕”。所以 T4 的 isReady 查询返回为 false,T4 不会执行。只有当 T3 执行完毕后更新状态为“正常结束”后,T4 的状态才为 ready,T4 才会开始运行。

其余的节点也以此类推,它们正常执行完毕的时候会在整个的 scenario 中广播该线程正常结束的信息,由主线程不断地轮询各个 ThreadEntry 的状态来开启各个线程。

这便是采用主控线程轮询状态表的方式来控制 Java 多线程运行框架的实现方式之一。

优点:概念结构清晰明了,实现简单。避免采用 Java 的锁机制,减少产生死锁的几率。当发生异常导致其中某些线程不能正常执行完毕的时候,不会产生挂起的线程。

缺点:采用主线程轮询机制,耗费 CPU 时间。当图中的节点太多的(n>??? 而线程单个线程执行时间比较短的时候 t<??? 需要进一步研究)时候会产生线程启动的些微延迟,也就是说实时性能在极端情况下不好,当然这可以另外写一篇文章来专门探讨。

“内” - wait&notify

相对于“外”-主线程轮询机制来说,“内”采用的是自我控制连锁触发机制。


图 5. 锁机制的静态类图
 

Thread 中的 lock 为当前 Thread 的 lock,lockList 是一个 HashMap,持有其后继线程的 lock 的引用,getLock 和 setLock 可以对 lockList 中的 Lock 进行操作。其中很重要的一个成员是 waitForCount,这是一个引用计数。表明当前线程正在等待的先决线程的个数,例如图 1 中所示的 T4,在初始的情况下,他等待的先决线程是 T2 和 T3,那么它的 waitForCount 等于 2。


图 6. 锁机制执行顺序图
 

当整个过程开始运行的时候,我们将所有的线程 start,但是每个线程所持的 lock 都处于 wait 状态,线程都会处于 waiting 的状态。此时,我们将 root thread 所持有的自身的 lock notify,这样 root thread 就会运行起来。当 root 的 run 方法执行完毕以后。它会检查其后续线程的 waitForCount,并将其值减一。然后再次检查 waitForCount,如果 waitForCount 等于 0,表示该后续线程的所有先决线程都已经执行完毕,此时我们 notify 该线程的 lock,该后续线程便可以从 waiting 的状态转换成为 running 的状态。然后这个过程连锁递归的进行下去,整个过程便会执行完毕。

我们还是以 T2,T3,T4 为例,当进行 initThreadLock 过程的时候,我们可以知道 T4 有两个直接先决线程 T2 和 T3,所以 T4 的 waitForCount 等于 2。我们假设 T3 先执行完毕,T2 仍然在 running 的状态,此时他会首先遍历其所有的直接后继线程,并将他们的 waitForCount 减去 1,此时他只有一个直接后继线程 T4,于是 T4 的 waitForCount 减去 1 以后值变为 1,不等于 0,此时不会将 T4 的 lock notify,T4 继续 waiting。当 T2 执行完毕之后,他会执行与 T3 相同的步骤,此时 T4 的 waitForCount 等于 0,T2 便 notify T4 的 lock,于是 T4 从 waiting 状态转换成为 running 状态。其他的节点也是相似的情况。

当然,我们也可以将整个过程的信息放在另外的一个全局对象中,所有的线程都去查找该全局对象来获取各自所需的信息,而不是采取这种分布式存储的方式。

优点:采用 wait&notify 机制而不采用轮询的机制,不会浪费CPU资源。执行效率较高。而且相对于“外”-主线程轮询的机制来说实时性更好。

缺点:采用 Java 线程 Object 的锁机制,实现起来较为复杂。而且采取一种连锁触发的方式,如果其中某些线程异常,会导致所有其后继线程的挂起而造成整个 scenario 的运行失败。为了防止这种情况的发生,我们还必须建立一套线程监控的机制来确保其正常运




线程的调度

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪些线程来执行。高优先级的线程会中断低优先级线程的执行。

在抢先式调度下,又分为时间片方式和非时间片方式,又叫独占方式。

在时间片方式下,当前活动的线程执行完时间片后,如果有其它同优先级的线程,则当前活动线程转入等待执行队列,等待下一个时间片的调度。

在非时间片方式下:当且活动线程一旦获得执行权,将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU,或者有高优先级的线程处于就绪状态。

在windows2000下的调度方式是抢先式和时间片方式相结合。

调度的核心问题就是,什么时候,哪一个线程占有CPU,什么时候放弃CPU。

一、放弃CPU的方式

在下面的情况下,当前线程会放弃CPU。

1、线程调用yield()或者sleep()方法主动放弃。

sleep()方法调用后,所有的其它线程都有执行的机会,包括低优先级的线程。

yield()方法,只给同优先级的线程执行的机会,如果没有同优先级的线程,则该方法不产生任何效果。

同学们要特别注意这两个方法的差别。

2、由于当前线程进行I/O访问、外存读写、等待用户输入等操作,导致线程阻塞。

3、抢先式系统下,有高优先级的线程参与调度;时间片方式下,当前时间片用完,有同优先级的线程参与调度。

Java中,线程的优先级用数字来表示,范围从1到10;1最低用常量Thread.MIN_PRIORITY表示,10最高,用Thread.MAX_PRIORITY表示。一个线程缺省优先级是5,用常量Thread.NORM_PRIORITY表示

可以用Thread类中的getPriority()获取线程的优先级,用setPrioriy()设置线程的优先级。



产生这种问题的原因在于对共享数据(临界资源)访问的操作的不完整性、非原子性。

解决方法有:互斥锁,

在Java中,每个对象都对应于一个可以称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

Java中提供了关键字synchronized来与对象的互斥锁联系。

如果synchronized用在类声明中,则表明该类中的所有方法都是synchronized的。

 在多线程的同步中使用了wait()和notify()方法来同步线程的执行,对这些方法的说明如下:
1、Wait,notify ,notifyAll必须在已经持有锁的情况下执行,所以它们只能出现在synchronized作用的范围内。
2、wait的作用:释放已持有的锁,当前线程进入wait队列。
3、notify 的作用:唤醒wait队列中的第一个线程并把它移入锁申请队列。
4、notifyAll的作用:唤醒wait队列中的所有的线程并把它们移入锁申请队列。


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 红米手机白屏怎么办 红米4黑屏打不开怎么办 oppo手机拨打电话时黑屏怎么办 华为手机拨打电话时黑屏怎么办 小米6x拨电话黑屏怎么办 魅族手机打电话黑屏怎么办 三星手机拨打电话时黑屏怎么办 华为畅享5打电话黑屏怎么办 小米note通话声音小怎么办 金立m5黑屏开不了机怎么办 金立手机光感器坏了怎么办 红掌根部烂了怎么办 魅族相册闪退怎么办 魅族手机闪退怎么办 苹果6手机主页面打不开怎么办 手机中病毒闪退怎么办 酷派手机开机黑屏怎么办啊 酷派手机黑屏打不开怎么办 酷派手机不开机怎么办 乐视手机开不了机怎么办 vivo手机拨号键盘不见了怎么办 华为手机拨号键盘不见了怎么办 金立手机拨号键盘不见了怎么办 酷派手机home键失灵怎么办 r11屏碎一半黑屏怎么办 金立手机黑屏打不开怎么办 小米8se自动跳出广告怎么办 小米手机总跳出广告怎么办 小米手机总是出现广告怎么办 红米1s开机黑屏怎么办 红米手机打电话黑屏怎么办 小米4c打游戏卡怎么办 电脑总出现拨号连接怎么办 win10电脑没有拨号连接怎么办 红米手机黑屏了怎么办 小米4s黑屏了怎么办 鼠标的左键失灵怎么办 小米5左键失灵怎么办 小米4左键失灵怎么办 小米5s左键失灵怎么办 单击鼠标左键就会自动删除?怎么办