18、Java并发性和多线程-饥饿与公平

以下内容转自http://ifeve.com/starvation-and-fairness/:

如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性”–即所有线程均能公平地获得运行机会。

 下面是本文讨论的主题:

1. Java中导致饥饿的原因:

  • 高优先级线程吞噬所有的低优先级线程的CPU时间。
  • 线程被永久堵塞在一个等待进入同步块的状态。
  • 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

2. 在Java中实现公平性方案,需要:

  • 使用锁,而不是同步块。
  • 公平锁。
  • 注意性能方面。

Java中导致饥饿的原因

在Java中,下面三个常见的原因会导致线程饥饿:

  1. 高优先级线程吞噬所有的低优先级线程的CPU时间。
  2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
  3. 线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

高优先级线程吞噬所有的低优先级线程的CPU时间

你能为每个线程设置独自的线程优先级,优先级越高的线程获得的CPU时间越多,线程优先级值设置在1到10之间,而这些优先级值所表示行为的准确解释则依赖于你的应用运行平台。对大多数应用来说,你最好是不要改变其优先级值。

线程被永久堵塞在一个等待进入同步块的状态

Java的同步代码区也是一个导致饥饿的因素。Java的同步代码区对哪个线程允许进入的次序没有任何保障。这就意味着理论上存在一个试图进入该同步区的线程处于被永久堵塞的风险,因为其他线程总是能持续地先于它获得访问,这即是“饥饿”问题,而一个线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。

线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象

如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

在Java中实现公平性

虽Java不可能实现100%的公平性,我们依然可以通过同步结构在线程间实现公平性的提高。

首先来学习一段简单的同步态代码:

public class Synchronizer{public synchronized void doSynchronized(){//do a lot of work which takes a long time
}
}

如果有一个以上的线程调用doSynchronized()方法,在第一个获得访问的线程未完成前,其他线程将一直处于阻塞状态,而且在这种多线程被阻塞的场景下,接下来将是哪个线程获得访问是没有保障的。

使用锁方式替代同步块

为了提高等待线程的公平性,我们使用锁方式来替代同步块。

public class Synchronizer{Lock lock = new Lock();public void doSynchronized() throws InterruptedException{this.lock.lock();//critical section, do a lot of work which takes a long timethis.lock.unlock();}
}

注意到doSynchronized()不再声明为synchronized,而是用lock.lock()和lock.unlock()来替代。

下面是用Lock类做的一个实现:

public class Lock {private boolean isLocked = false;private Thread lockingThread = null;public synchronized void lock() throws InterruptedException {while (isLocked) {wait();}isLocked = true;lockingThread = Thread.currentThread();}public synchronized void unlock() {if (this.lockingThread != Thread.currentThread()) {throw new IllegalMonitorStateException("Calling thread has not locked this lock");}isLocked = false;lockingThread = null;notify();}
}

注意到上面对Lock的实现,如果存在多线程并发访问lock(),这些线程将阻塞在对lock()方法的访问上。另外,如果锁已经锁上(校对注:这里指的是isLocked等于true时),这些线程将阻塞在while(isLocked)循环的wait()调用里面。要记住的是,当线程正在等待进入lock() 时,可以调用wait()释放其锁实例对应的同步锁,使得其他多个线程可以进入lock()方法,并调用wait()方法。

这回看下doSynchronized(),你会注意到在lock()和unlock()之间的注释:在这两个调用之间的代码将运行很长一段时间。进一步设想,这段代码将长时间运行,和进入lock()并调用wait()来比较的话。这意味着大部分时间用在等待进入锁和进入临界区的过程是用在wait()的等待中,而不是被阻塞在试图进入lock()方法中。

在早些时候提到过,同步块不会对等待进入的多个线程谁能获得访问做任何保障,同样当调用notify()时,wait()也不会做保障一定能唤醒线程(至于为什么,请看线程通信)。因此这个版本的Lock类和doSynchronized()那个版本就保障公平性而言,没有任何区别。

但我们能改变这种情况。当前的Lock类版本调用自己的wait()方法,如果每个线程在不同的对象上调用wait(),那么只有一个线程会在该对象上调用wait(),Lock类可以决定哪个对象能对其调用notify(),因此能做到有效的选择唤醒哪个线程。

公平锁

下面来讲述将上面Lock类转变为公平锁FairLock。你会注意到新的实现和之前的Lock类中的同步和wait()/notify()稍有不同。

准确地说如何从之前的Lock类做到公平锁的设计是一个渐进设计的过程,每一步都是在解决上一步的问题而前进的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。这些本身的讨论虽已超出本文的范围,但其中每一步的内容都将会专题进行讨论。重要的是,每一个调用lock()的线程都会进入一个队列,当解锁后,只有队列里的第一个线程被允许锁住Farlock实例,所有其它的线程都将处于等待状态,直到他们处于队列头部。

public class FairLock {private boolean isLocked = false;private Thread lockingThread = null;private List<QueueObject> waitingThreads = new ArrayList<QueueObject>();public void lock() throws InterruptedException {QueueObject queueObject = new QueueObject();boolean isLockedForThisThread = true;synchronized (this) {waitingThreads.add(queueObject);}while (isLockedForThisThread) {synchronized (this) {isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject;if (!isLockedForThisThread) {isLocked = true;waitingThreads.remove(queueObject);lockingThread = Thread.currentThread();return;}}try {queueObject.doWait();} catch (InterruptedException e) {synchronized (this) {waitingThreads.remove(queueObject);}throw e;}}}public synchronized void unlock() {if (this.lockingThread != Thread.currentThread()) {throw new IllegalMonitorStateException("Calling thread has not locked this lock");}isLocked = false;lockingThread = null;if (waitingThreads.size() > 0) {waitingThreads.get(0).doNotify();}}
}public class QueueObject {private boolean isNotified = false;public synchronized void doWait() throws InterruptedException {while (!isNotified) {this.wait();}this.isNotified = false;}public synchronized void doNotify() {this.isNotified = true;this.notify();}public boolean equals(Object o) {return this == o;}}

首先注意到lock()方法不在声明为synchronized,取而代之的是对必需同步的代码,在synchronized中进行嵌套。

FairLock新创建了一个QueueObject的实例,并对每个调用lock()的线程进行入队列。调用unlock()的线程将从队列头部获取QueueObject,并对其调用doNotify(),以唤醒在该对象上等待的线程。通过这种方式,在同一时间仅有一个等待线程获得唤醒,而不是所有的等待线程。这也是实现FairLock公平性的核心所在。

请注意,在同一个同步块中,锁状态依然被检查和设置,以避免出现滑漏条件。

还需注意到,QueueObject实际是一个semaphore。doWait()和doNotify()方法在QueueObject中保存着信号。这样做以避免一个线程在调用queueObject.doWait()之前被另一个调用unlock()并随之调用queueObject.doNotify()的线程重入,从而导致信号丢失。queueObject.doWait()调用放置在synchronized(this)块之外,以避免被monitor嵌套锁死,所以另外的线程可以解锁,只要当没有线程在lock方法的synchronized(this)块中执行即可。

最后,注意到queueObject.doWait()在try – catch块中是怎样调用的。在InterruptedException抛出的情况下,线程得以离开lock(),并需让它从队列中移除。

性能考虑

如果比较Lock和FairLock类,你会注意到在FairLock类中lock()和unlock()还有更多需要深入的地方。这些额外的代码会导致FairLock的同步机制实现比Lock要稍微慢些。究竟存在多少影响,还依赖于应用在FairLock临界区执行的时长。执行时长越大,FairLock带来的负担影响就越小,当然这也和代码执行的频繁度相关。

转载于:https://www.cnblogs.com/EasonJim/p/7026895.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/400658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenSceneGraph 3.2 版本修改点

OpenSceneGraph-3.2.0稳定版本发布了&#xff0c;改善了对iOS、Android的支持&#xff0c;支持OpenGL的更多新特性。可以通过 下载版块来进行下载。 OpenSceneGraph 3.2 发布. 版本修改点: 全面对OpenGL ES 1.1 和 2.0 的支持&#xff0c;包括各种扩展。改善QTKit, imageio 以及…

【机器视觉学习笔记】双边滤波算法(C++)

目录源码滤波器主函数效果完整源码平台&#xff1a;Windows 10 20H2 Visual Studio 2015 OpenCV 4.5.3 本文所用源码修改自双边滤波(bilateral filter)以及联合双边滤波&#xff08;joint bilateral filter&#xff09;—— flow_specter 源码 滤波器 // 双边滤波 // src…

第二章:图像处理基础

第二章&#xff1a;图像处理基础操作一、图像的基本表示方法&#xff1a;1. 二值图像&#xff1a;2. 灰度图像&#xff1a;3. 彩色图像&#xff1a;二、像素处理&#xff1a;1. 二值图像及灰度图像&#xff1a;2.彩色图像&#xff1a;3. 使用numpy.array访问像素&#xff1a;三…

《Head First设计模式》 读书笔记16 其余的模式(二) 蝇量 解释器 中介者

《Head First设计模式》 读书笔记16 其余的模式&#xff08;二&#xff09; 蝇量 解释器 中介者 蝇量&#xff08;Flyweight Pattern&#xff09; 如想让某个类的一个实例能用来提供许多“虚拟实例”&#xff0c;就使用蝇量模式&#xff08;Flyweight Pattern&#xff09; 。 例…

洛谷P1525 关押罪犯

P1525 关押罪犯 题目描述 S 城现有两座监狱&#xff0c;一共关押着N 名罪犯&#xff0c;编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久&#xff0c;如果客观条件具备则随时可能爆发冲突。我们用“怨气值”&#xff08;一个正整数值&#xff09;来表示…

【机器视觉学习笔记】Hough变换直线检测(C++)

目录源码效果平台&#xff1a;Windows 10 20H2 Visual Studio 2015 OpenCV 4.5.3 本文源码摘自OpenCV2马拉松第22圈——Hough变换直线检测原理与实现 源码 #include <opencv2\opencv.hpp> #include <iostream> #include <opencv2\imgproc\types_c.h> #in…

第3章:图像运算

第3章&#xff1a;图像运算one. 图像加法运算&#xff1a;1. 加号运算符"":2. cv2.add()函数&#xff1a;two. 图像加权和&#xff1a;three. 按位逻辑运算&#xff1a;1. 按位与运算&#xff1a;2. 按位或运算&#xff1a;3.按位非运算&#xff1a;4. 按位异或运算&…

分页代码

/// <summary>/// 获得伪静态页码显示链接/// </summary>/// <param name"curPage">当前页数</param>/// <param name"countPage">总页数</param>/// <param name"url">超级链接地址</param>//…

JMS中的消息通信模型

1. MQ简介&#xff1a; 消息队列&#xff08;Message Queue&#xff0c;简称MQ&#xff09;,是应用程序与应用程序之间的一种通信方法。应用程序通过发送和检索出入列队的针对应用程序的数据 - 消息来通信&#xff0c;而无需专用连接来链接它们。程序之间通过在消息中发送数据进…

【机器视觉学习笔记】最近邻插值实现图片任意角度旋转(C++)

目录原理源码RotateImage主函数效果完整源码速度优化源码优化效果平台&#xff1a;Windows 10 20H2 Visual Studio 2015 OpenCV 4.5.3 本文算法改进自图形算法与实战&#xff1a;6.图像运动专题&#xff08;5&#xff09;图像旋转-基于近邻插值的图像旋转 —— 进击的CV 原理…

UGUI的优点新UI系统

UGUI的优点新UI系统 第1章 新UI系统概述 UGUI的优点新UI系统&#xff0c;新的UI系统相较于旧的UI系统而言&#xff0c;是一个巨大的飞跃&#xff01;有过旧UI系统使用体验的开发者&#xff0c;大部分都对它没有任何好感&#xff0c;以至于在过去的很长一段时间里&#xff0c;大…

【探索HTML5第二弹05】响应式布局(中),一步一步响应式布局

前言 前天初步探究了一次响应式布局&#xff0c;虽然花了一天功夫&#xff0c;做出来的东西还是不行&#xff0c;在此我还是认为要做响应式布局设计应该先行&#xff0c;应该先制作3个以上的设计图出来&#xff0c;但是对于手机来说&#xff0c;图片流量也是个问题&#xff0c;…

通过使用CSS字体阴影效果解决hover图片时显示文字看不清的问题

1.前言 最近需要加入一个小功能&#xff0c;在鼠标越过图片时&#xff0c;提示其大小和分辨率&#xff0c;而不想用增加属性title来提醒&#xff0c;不够好看。然而发现如果文字是一种颜色&#xff0c;然后总有概率碰到那张图上浮一层的文字会看不到&#xff0c;所以加入文字字…

第4章:色彩空间类型转换

第四章&#xff1a;色彩空间类型转换one. 色彩空间基础知识&#xff1a;1. GRAY色彩空间&#xff1a;2. XYZ色彩空间3. YCrCb色彩空间3. HSV色彩空间4. HLS 色彩空间5. CIEL * a * b *色彩空间6. CIEL * u * v *色彩空间7. Bayer色彩空间two. 类型转换函数&#xff1a;three. 类…

【机器视觉学习笔记】双线性插值实现图片任意角度旋转(C++)

目录原理源码RotateImage_BilinearInterpolation主函数效果与最近邻插值比较原图最近邻插值效果&#xff08;局部&#xff09;双线性插值效果&#xff08;局部&#xff09;完整源码平台&#xff1a;Windows 10 20H2 Visual Studio 2015 OpenCV 4.5.3 原理 如图所示&#xff0…

高德地图调用和添加标注

看过高德地图API的同学都知道&#xff0c;高德地图不同端调用是不一样的&#xff0c;作为一个前端菜鸟&#xff0c;前一阵分别在pc端和移动端分别调用了高德地图。情况是这个样子的&#xff0c;PC端呢我们可以用高德API的web端的javascript代码。调用没有问题&#xff0c;具体是…

第5章 - 几何变换

第五章-几何变换one. 缩放:two. 翻转&#xff1a;three. 仿射&#xff1a;1. 平移&#xff1a;2. 旋转&#xff1a;3. 更多复杂的仿射变换&#xff1a;four. 透视&#xff1a;five. 重映射&#xff1a;1. 映射参数的理解&#xff1a;2. 复制&#xff1a;3. 绕x轴旋转&#xff1…

安装设置Android Studio Win7安装

发一下牢骚和主题无关&#xff1a; 让人等待已久的Google I/O 2013 大会没有给我们带来Android5.0&#xff0c;也没有带来Adnroid4.3等等&#xff0c;但带来了Android Studio&#xff0c;虽说是预览版&#xff0c;又是基于Intellij IDEA&#xff0c; 但是也无不让开辟者们高兴。…

【树莓派学习笔记】一、烧录系统、(无屏幕)配置Wifi和SSH服务

目录系统镜像的准备格式化TF卡烧录镜像配置Wifi开启SSH服务第一次开机平台&#xff1a;树莓派3B 版本&#xff1a; 2021-05-07-raspios-buster-armhf 系统镜像的准备 树莓派资源里有许多资源&#xff0c;包括我们要用到的镜像。 格式化TF卡 将TF卡格式化为FAT32格式。 …

Linux中Oracle的sqlplus下退格和Del键无效的问题解决

利用rlwrap工具解决方法 1、安装rlwrap和readline库 CentOS下可以用EPEL的yum源直接安装&#xff0c;步骤如下&#xff1a; &#xff08;1&#xff09;RHEL/CentOS/SL Linux 6.x 下安装 EPEL6 yum源&#xff1a; 32位系统选择&#xff1a; # rpm -ivh http://download.fedorap…