线程之活跃度失败(死锁、活锁、饥饿)

线程活跃度

活跃度问题是指线程或进程长时间得不到cpu占用。《Java并发编程实战》中提到,无论执行计算密集操作还是执行某个可能阻塞的操作,如果持有锁的时间过长,都会带来活跃性或性能问题。

活跃度失败有那几种

  • 死锁也就是互相等着对方释放资源,结果谁也得不到。

  • 活锁可能发生让某一个线程一直处于等待状态,其他线程都可以调用到。

  • 饥饿我就感觉用抢占式说好说,每次来就执行优先级高的,那么优先级低的可能永远执行不到。

死锁

多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

死锁产生的原因

  • 系统资源的竞争

    通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在 运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争 才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。

  • 进程推进顺序非法

    进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都 会因为所需资源被占用而阻塞。

    信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁

死锁产生的必要四个条件

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有。

写一个死锁的例子

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        ////模拟线程1占用资源1并申请获得资源2的锁
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                synchronized (ThreadResource.resource2){
                    System.out.println("LockThread1 lock resource2");
                }
                System.out.println("LockThread1 release resource2");
            }
            System.out.println("LockThread1 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
    }
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        //模拟线程2占用资源2并申请获得资源1的锁:
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待线程1锁定资源1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread2 lock resource1");
                }
                System.out.println("LockThread2 release resource1");
            }
            System.out.println("LockThread2 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class ThreadResource {
    public static Object resource1 = new Object();
    public static Object resource2 = new Object();
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class DeadLock{
    public static void main(String[] args) {
        new Thread(new LockThread1()).start();
        new Thread(new LockThread2()).start();
    }
}

运行结果

Thread1 is running
Thread2 is running
Thread1 lock resource1
Thread2 lock resource2
​
并且程序一直无法结束。这就是由于线程1占用了资源1,此时线程2已经占用资源2,这个时候线程1想要使用资源2,线程2想要使用资源1。两个线程都无法让步,导致程序死锁。

如何避免死锁

死锁是可以避免的。用于避免死锁的技术三种方式:

  • 加锁顺序(线程按照一定的顺序加锁)

  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

  • 死锁检测

由上面的例子可以看出当线程在同步某个对象里,再去锁定另外一个对象的话,就和容易发生死锁的情况。最好是线程每次只锁定一个对象并且在锁定该对象的过程中不再去锁定其他的对象,这样就不会导致死锁了。比如将以上的线程改成下面这种写法就可以避免死锁

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
            }
            System.out.println("LockThread1 release resource1");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread1 lock resource2");
            }
            System.out.println("LockThread1 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
}

/**
 * @Author 安仔夏天勤奋
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待线程1锁定资源1
            }
            System.out.println("LockThread2 release resource2");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread2 lock resource1");
            }
            System.out.println("LockThread2 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

运行结果

LockThread1 is running
LockThread1 lock resource1
LockThread2 is running
LockThread2 lock resource2
LockThread1 release resource1
LockThread1 lock resource2
LockThread1 release resource2
LockThread1 is stop
LockThread2 release resource2
LockThread2 lock resource1
LockThread2 release resource1
LockThread2 is stop

如果需要同时去锁定两个对象,可以根据加锁顺序定义一个先后的规则。按照上面的例子,需要同时锁定两个资源,可以根据资源的hashcode值大小来判断先后锁定顺序。代码如下:

public class LockThread3 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread3 is running");
            if ( ThreadResource.resource1.hashCode() > ThreadResource.resource2.hashCode() ) {
                //先锁定resource1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread3 lock resource1");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource2) {
                        System.out.println("LockThread3 lock resource2");
                    }
                    System.out.println("LockThread3 release resource2");
                }
                System.out.println("LockThread3 release resource1");
            }else {
                //先锁定resource2
                synchronized (ThreadResource.resource2) {
                    System.out.println("LockThread3 lock resource2");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource1) {
                        System.out.println("LockThread3 lock resource1");
                    }
                    System.out.println("LockThread3 release resource1");
                }
                System.out.println("LockThread3 release resource2");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread3 is stop");
    }
}

死锁更详细可参照

活锁

活锁是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

活锁不会被阻塞,而是不停检测一个永远不可能为真的条件。除去进程本身持有的资源外,活锁状态的进程会持续耗费宝贵的CPU时间。

举个例子,两个人在走廊上碰见,大家都互相很有礼貌,互相礼让,A从左到右,B也从从左转向右,发现又挡住了地方,继续转换方向,但又碰到了,反反复复,一直没有机会运行下去。

活锁例子

活锁的解决方法

  • 调整重试机制。

  • 引入一些随机性。

活锁和死锁的区别

  • 活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待。

  • 活锁有可能自行解开,死锁则不能。

饥饿

饥饿是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。

线程长时间无法获得共享资源从而继续相继的处理。这种情况经常发生在当共享资源被“贪婪”线程长时间占据时。假设一个对象提供的互斥方法需要很长时间处理才能返回,然而如果某线程老是频繁激活这个方法,那么其他需要访问该对象的线程就会被长时间阻塞,而处于饥饿状态。

饥饿.png

Java中的读写锁的实现类ReentranctReadWriteLock,在默认使用非公平模式(不是先来先处理的模式)的情况下,如果某个线程想要读取资源,只要没有线程正在对该资源进行写操作且没有线程请求对该资源的写操作即可。如果读操作发生的比较频繁,我们又没有提升写操作的优先级,那么就会产生“饥饿”现象。请求写操作的线程会一直阻塞,直到所有的读线程都从ReentranctReadWriteLock上解锁了。如果一直保证新线程的读操作权限,那么等待写操作的线程就会一直阻塞下去,结果就发生了“饥饿”。

java代码会引起这种类型的饥饿

synchronized(obj) {
 while (true) {
 // .... infinite loop
 }
}

优先级引起也会引起线程饥饿

高优先级线程吞噬所有的低优先级线程的CPU时间。例如在java中调用了Thread.setPriority方法设置了线程优先级,优先级低的线程始终得不到执行的机会,虽然线程优先级对于不同操作系统的实现方式不一样,即便设置了优先级也不一定会有效果,但还是有可能会出现这种情况。

饥饿的解决办法有

  • 提升写请求的优先级或者采用公平策略。

  • 在synchronized方法或者块中避免无限循环。

  • 采用线程默认的优先级。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,629评论 2 17
  • 1、竞态条件: 定义:竞态条件指的是一种特殊的情况,在这种情况下各个执行单元以一种没有逻辑的顺序执行动作,从而导致...
    Hughman阅读 1,283评论 0 7
  • 1.解决信号量丢失和假唤醒 public class MyWaitNotify3{ MonitorObject m...
    Q罗阅读 871评论 0 1
  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,088评论 0 23
  • ## 研究是一种能力 在我读研究生的时候,才有了第一次真正接触到“研究能力”这个概念。和同学们相比,连最初到问题如...
    都市牛阅读 205评论 0 0