Android 8.1 源码_线程篇 -- 浅析多线程中的 “同步” 和 “死锁” 问题

Thread - 同步

问题引出

我们现在来通过Runnable接口实现多线程,产生3个线程对象,模拟卖票的场景!

class MyThread implements Runnable {
    private int ticket = 5;
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (ticket > 0) {
                try {
                    Thread.sleep(300);     // 加入延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("卖票:ticket = " + ticket--);
           }
        }
    }
};

public class SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.start();
    }
}

我们执行下这段代码,结果如下:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 5
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1    // 票数竟然还能负数?

为什么会出现“负数”的情况:在上面的操作中,我们可以发现,因为加入了“延迟操作”一个线程很有可能在还没对票数进行减操作之前,其他线程就已经将票数减少了,这样就会出现票数为负的情况。

有没有方法解决?肯定是有的!想解决这样的问题,就必须使用同步!所谓同步,就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

解决问题

解决资源共享的同步操作,有两种方法:同步代码块同步方法

同步代码块

所谓代码块就是指使用“{}”括起来的一段代码,如果在代码块上加上synchronized关键字,则此代码块就成为同步代码块。

【同步代码块 - 格式】

synchronized(同步对象) {
    需要同步的代码 ;
}

我们对代码进行修改:

class MyThread implements Runnable {
    private int ticket = 5;
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (this) {              // 加入同步操作
                if (ticket > 0) {
                    try {
                        Thread.sleep(300);     // 加入延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("卖票:ticket = " + ticket--);
               }
            }
        }
    }
};

public class SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.start();
    }
}

我们重新执行下这段代码,结果如下:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

同步方法

除了可以将需要的代码设置成同步代码块外,也可以使用synchronized关键字将一个方法声明成同步方法。

【同步方法 - 格式】

synchronized 方法返回值 方法名称(参数列表) {
}

我们采用同步方法对代码进行修改:

class MyThread implements Runnable {
    private int ticket = 5;
    public void run() {
        for (int i = 0; i < 100; i++) {
            this.sale();                // 调用同步方法
        }
    }
    public synchronized void sale() {   // 声明同步方法
        if (ticket > 0) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("卖票:ticket = " + ticket--);
        }
    }
};

public class SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.start();
    }
}

我们重新执行下这段代码,结果如下:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

从以上程序的运行结果可以发现,此代码完成了与之前同步代码块同样的功能。

总结

多个线程共享同一资源时需要进行同步,以保证资源操作的完整性。

Thread - 死锁

通过上面的例子,我们发现,同步还是很有好处的,它可以保证资源共享操作的正确性,但是过多的同步也会产生问题,这就是我们接下来要讨论“死锁”问题!

什么是死锁?

多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题 -- 死锁

在编写多线程的时候,必须要注意资源的使用问题,如果两个或多个线程分别拥有不同的资源,而同时又需要对方释放资源才能继续运行时,就会发生死锁。

简单来说:死锁就是当一个或多个进程都在等待系统资源,而资源本身又被占用时,所产生的一种状态。

造成死锁的原因

多个线程竞争共享资源,由于资源被占用,资源不足或进程推进顺序不当等原因造成线程处于永久阻塞状态,从而引发死锁。

造成死锁的四个条件

1、互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2、请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3、不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4、循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

问题引出

现在张三想要李四的画,李四想要张三的书,于是产生了以下对话:

张三对李四说:“把你的画给我,我就给你书”
李四对张三说:“把你的书给我,我就给你画”

此时,张山在等着李四的答复,李四也在等着张三的答复,那么这样下去的结果就是,两个人都在等待,但是都没有结果,这就是“死锁”!

从线程角度来说,所谓死锁就是指两个线程都在等待彼此先完成,造成了程序的停滞,一般程序的死锁都是在程序运行时出现,比如我们通过一个代码范例来看看发生死锁的场景。

class Zhangsan {
    public void say() {
        System.out.println("Zhangsan say: give me your painting, i will give you my book!");
    }
    
    public void get() {
        System.out.println("Zhangsan got Lisi's painting!");
    }
}

class Lisi {
    public void say() {
        System.out.println("Lisi say: give me your book, i will give you my painting!");
    }
    
    public void get() {
        System.out.println("Lisi got Zhangsan's book!");
    }
}

public class ThreadDeadLock implements Runnable {
    private static Zhangsan zs = new Zhangsan();         // 实例化static型对象,数据共享
    
    private static Lisi ls = new Lisi();                 // 实例化static型对象,数据共享
    
    private boolean flag = false;                        // 声明标记,用于判断哪个对象先执行
    
    public void run() {
        if (flag) {                                      // 判断标志位,flag为true,Zhangsan先执行
            synchronized (zs) {                          // 同步第一个对象
                zs.say();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (ls) {                      // 同步第二个对象
                    zs.get();
                }
            }
        } else {                                         // Lisi先执行
            synchronized (ls) {                          // 同步第二个对象
                ls.say();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (zs) {                      // 同步第一个对象
                    ls.get();
                }
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadDeadLock t1 = new ThreadDeadLock();
        ThreadDeadLock t2 = new ThreadDeadLock();
        t1.flag = true;
        t2.flag = false;
        Thread thA = new Thread(t1);
        Thread thB = new Thread(t2);
        thA.start();
        thB.start();
    }
}

我们执行下这段代码,结果如下:

Zhangsan say: give me your painting, i will give you my book!
Lisi say: give me your book, i will give you my painting!

从程序的运行结果中可以看出,两个线程都在彼此等待着对方的执行完成,这样,程序就无法向下继续执行,从而造成了死锁的现象。

解决问题

要预防和避免死锁的发生,只需将上面所讲到的4个条件破坏掉其中之一即可。

如上面的代码当中,有四个同步代码块,只需要将其中一个同步代码块去掉,即可解决死锁问题,一般而言破坏“循环等待”这个条件是解决死锁最有效的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章