zoukankan      html  css  js  c++  java
  • java多线程(八)-死锁问题和java多线程总结

    为了防止对共享受限资源的争夺,我们可以通过synchronized等方式来加锁,这个时候该线程就处于阻塞状态,设想这样一种情况,线程A等着线程B完成后才能执行,而线程B又等着线程C,而线程C又等着线程A。这三个任务之间相互循环等待,但是其实没有哪个任务能够执行,这种情况就发生了死锁。

    有一个经典的哲学家就餐问题,可以更清晰的理解死锁问题。有N个哲学家围绕在一张圆形餐桌前,餐桌中间有一份面条,每个哲学家都只有一根筷子,放在他的左手边。(因为餐桌是圆形的,所以就是每两个哲学家之间有一根筷子),所以共计有N个哲学家,N个筷子, 哲学们家们有时候思考,思考时不需要获取其他共享资源;有时候吃面条,吃面条时需要两根筷子,哲学家需要先拿到他right手边的筷子,然后再去拿left手边的筷子。如果此时,left手边筷子不在桌子上(被边上的哲学家拿走了)。则哲学家就把right手边的筷子拿在手中等待。 (调用wait).等待left边的筷子被放下。如果哲学家吃完面条,则放下两根筷子 ,继续思考。


    我们仅仅通过逻辑思考,就可以想到如果每个哲学家都拿到了他right手边的筷子,那么此时就发生了死锁,因为实际上桌子上,每个哲学家正好拿到了一根筷子,都在等待他left手边的筷子被放下,但是不会再有筷子被放下了.

    代码demo:src hread_runnableDeadLockingDiningPhiosophers.java

     1 class Chopstick{
     2     private boolean taken = false;
     3     //拿起筷子
     4     public synchronized void take() throws InterruptedException{
     5         while (taken){
     6             wait();
     7         }
     8         TimeUnit.MILLISECONDS.sleep(100);
     9         taken = true;
    10     }
    11     //放下筷子
    12     public synchronized void drop(){
    13         taken = false;
    14         notify();
    15     }
    16 } //end of "class Chopstick"
    17 
    18 class Philosopher implements Runnable{
    19     private Chopstick left;
    20     private Chopstick right; 
    21     private final int id;
    22     private int eatTime;
    23     private int thinkTime;
    24     private Random rand = new Random(42); //the Answer to Life, the Universe and Everything is 42
    25     
    26     public Philosopher(Chopstick left, Chopstick right, int id, int eatTime, int thinkTime) {
    27         super();
    28         this.left = left;
    29         this.right = right;
    30         this.id = id;
    31         this.eatTime = eatTime;
    32         this.thinkTime = thinkTime;
    33     }
    34     
    35     //思考/或者吃饭的一段时间。
    36     private void pause(int time) throws InterruptedException{
    37         TimeUnit.MILLISECONDS.sleep(rand.nextInt(time*20));
    38     }
    39 
    40     @Override
    41     public void run() {
    42         // TODO Auto-generated method stub
    43         try {
    44             while (!Thread.interrupted()){
    45                 System.out.println(this + "thinking");
    46                 pause(thinkTime);
    47                 //哲学家开始吃饭了
    48                 System.out.println(this + "grabbing right");
    49                 right.take();
    50                 System.out.println(this + "grabbing left");
    51                 left.take();
    52                 System.out.println(this + "grabbing eating");
    53                 pause(eatTime);
    54                 //吃完了。可以放下筷子了
    55                 right.drop();
    56                 left.drop();
    57                 
    58             }
    59         } catch (InterruptedException e) {
    60             // TODO: handle exception
    61             System.out.println(this + "  exiting via interrupt");
    62         }
    63     }
    64 
    65     @Override
    66     public String toString() {
    67         return "Philosopher id=" + id + "	"; 
    68     }
    69     
    70     
    71     
    72 }//end of "class Philosopher"
    73 
    74 public class DeadLockingDiningPhiosophers {
    75     //哲学家和筷子的数量
    76     private static final int N = 3;
    77     private static final int eatTime = 20;
    78     private static final int thinkTime = 3;
    79     
    80     public static void main(String[] args) throws Exception{
    81         ExecutorService exec = Executors.newCachedThreadPool();
    82         int ponder = 1;
    83         Chopstick[] sticks = new Chopstick[N];
    84         
    85         for (int i=0; i<N; i++){
    86             sticks[i] = new Chopstick();
    87         }
    88         
    89         for (int i=0; i<N; i++){
    90             exec.execute(new Philosopher(sticks[i], sticks[(i+1)%N], i, eatTime, thinkTime));
    91         }
    92         
    93     }
    94 
    95 }

    代码分析: Chopstick对象有 take(拿起)和 drop(放下)两个动作,而哲学家对象呢,不管是吃饭过程,还是思考过程,都是模拟sleep随机的时间, 吃完饭之后,放下筷子,进行思考。不间断进行循环。

    在demo中,3个线程,分别执行3个哲学家的任务, 同时也只有3个筷子。
    按照我们的测试,有概率会发生死锁。为了增大死锁发生的概率,便于测试,我们将拿起筷子的时间延长了。(就是在take方法中sleep(100)).

    进行测试,很快就发生了死锁。
    其中一次的输出结果:

    从控制台可以看出,程序一直在运行,但是哲学家们却不会再吃饭和思考了。
    从输出信息看出,
    1,0,2号哲学家依次拿起了right边的筷子 ,然后再准备拿起left边的筷子时,因为没有筷子了,而陷入了漫长的wait()中,这个时候,死锁发生了。程序死掉了。

    对于哲学家就餐问题,我们可以想出一个避免死锁的方案,比如,对于其中的某一位哲学家,限定其先拿left边的筷子,再拿right边的筷子。(和其余的哲学家正好相反)。

    死锁问题最难的地方是在于它是小概率性的,并且可能隐藏相当长的时间才会发生,并且每次发生死锁时,都是不可重现的。这在实际的项目中,会引起非常难以调试的bug。
    而在实际项目中,必现的bug都容易解决,小概率的,不可重现的bug那才真的让人头疼。

    程序避免死锁并不是件容易的事情,但是遵循以下原则则可以尽量避免死锁。
    (1),使用锁的时间尽可能的短,考虑使用同步语句块来代替同步方法。
    (2),尽量避免代码在同一个时刻需要多个锁。
    (3),创建和使用一个大锁来代替若干把小锁,并且用这把锁用于互斥。


    总结:
    多线程问题算是java当中比较高级的内容了,当然因为能力有限,我的这几篇博客写的也非常肤浅。而实际编码中,是否应该使用多线程,也应该仔细斟酌。
    使用多线程应该基于以下几个原因。
    (1),处理交织在一起的很多任务。
    (2),更高效的应用计算机资源。(比如多核cpu,等待I/O),
    (3),更好的组织代码。
    (4)更好的用户体验。(比如UI界面)

    但是多线程也有一些缺点要注意。
    (1),等待共享资源时,降低效率。
    (2),上下文切换需要耗费额外的资源。
    (3),多线程也会增加代码复杂度。
    (4),可能会导致一些难以调试的bug。比如死锁。
    (5),平台差异性。


    如果线程问题过于复杂,java的多线程机制不能满足要求,那么应该使用类似Erlang这样的 专门面向并发的的函数性语言。

    这几篇java多线程文章的demo代码下载地址 http://download.csdn.net/detail/yaowen369/9786452

    ---

    作者: www.yaoxiaowen.com
    github: https://github.com/yaowen369

  • 相关阅读:
    Mongo库表占用空间统计
    修改elasticsearch默认索引返回数
    针对docker中的mongo容器增加鉴权
    自动化测试框架STAF介绍
    单点登陆思想
    Django请求流程
    python冒泡排序,可对list中的字典进行排序
    python合并list中各个字典中相同条件的累计数
    哎,linux nginx命令就是记不住啊
    python利用urllib2读取webservice接口数据
  • 原文地址:https://www.cnblogs.com/yaoxiaowen/p/6581339.html
Copyright © 2011-2022 走看看