Java线程的同步与死锁

作者: 一个有故事的程序员 | 来源:发表于2017-09-27 22:46 被阅读57次

    导语

    本篇内容了解就OK了。重点有一个关键字synchronized。

    主要内容

    • 线程同步产生原因
    • 线程的同步处理操作
    • 线程的死锁情况

    具体内容

    同步问题的引出

    实际上所谓的同步指的就是多个线程访问同一资源时所需要考虑到的问题。

    范例:观察非同步情况下的操作

    class MyThread implements Runnable {
        private int ticket = 5;  // 一共有5张票
        public void run() {
            for(int i = 0; i < 20; i++) {
                if(this.ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "  卖票,ticket = " + this.ticket--);
                }
            }
        }
    }
    
    public class TestDemo {
        public static void main(String args[]) {
            MyThread mt = new MyThread();
            new Thread(mt, "票贩子A").start();
            new Thread(mt, "票贩子B").start();
            new Thread(mt, "票贩子C").start();
            new Thread(mt, "票贩子D").start();
        }
    }
    

    现在四个线程对象应该一起销售出5张票。
    输出结果(情况随机)

    票贩子D  卖票,ticket = 5
    票贩子A  卖票,ticket = 2
    票贩子B  卖票,ticket = 4
    票贩子C  卖票,ticket = 3
    票贩子D  卖票,ticket = 1
    

    此时没有出现问题是因为是在一个JVM进程下运行,并且没有受到任何的影响,如果想要观察到问题,可以加入一个延迟。

    修改代码

    class MyThread implements Runnable {
        private int ticket = 5;  // 一共有5张票
        public void run() {
            for(int i = 0; i < 20; i++) {
                if(this.ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "  卖票,ticket = " + this.ticket--);
                }
            }
        }
    }
    

    输出结果(情况随机)

    票贩子B  卖票,ticket = 4
    票贩子A  卖票,ticket = 3
    票贩子C  卖票,ticket = 5
    票贩子D  卖票,ticket = 2
    票贩子A  卖票,ticket = 1
    票贩子B  卖票,ticket = 0
    票贩子C  卖票,ticket = -1
    票贩子D  卖票,ticket = -2
    

    此时执行之后发现操作的结果出现了负数,那就是不同步的情况,到底是如何造成不同步呢?

    整个卖票分为两步:

    • 第一步:判断是否有剩余票。
    • 第二步:修改剩余票数。

    当票还有一张的时候,第一个线程进入判断语句,开始休眠,另一个线程也进入判断语句内,100毫秒后,票数都减1,所以就出现了负数。

    这种情况属于异步操作,异步操作就容易出现安全隐患。

    同步操作

    通过观察可以发现以上程序所带来的最大问题在:判断和修改数据是分开完成的,即,某几个线程可以同时执行这些功能。

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

    在Java里面如果想要实现线程的同步可以使用synchronized关键字。而这个关键字可以通过两种方式使用:

    • 同步代码块。
    • 同步方法。

    范例:观察同步块解决

    class MyThread implements Runnable {
        private int ticket = 50;
        public void run() {
            for(int i = 0; i < 200; i++) {
                synchronized(this) {  // 当前操作每次只允许一个对象进入
                    if(this.ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "  卖票,ticket = " + this.ticket--);
                    }
                }
            }
        }
    }
    

    输出比较长,发现可以解决问题,但是这样的写法比较麻烦,我们可以使用同步方法解决。

    范例:同步方法解决

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

    输出结果和之前没有区别。

    同步操作与异步操作相比,异步操作的执行速度要高于同步操作,但是同步操作时数据的安全性越高,属于安全的线程操作。

    死锁

    实际上通过发现,所谓的同步指的就是一个线程对象等待另外一个线程对象执行完毕后的操作形式。线程同步过多就有可能造成死锁。
    下面编写一个程序,本程序无实际意义,观察死锁的情况。

    范例:观察死锁

    class A {
        public synchronized void say(B b) {
            System.out.println("我说:把你的本给我,我给你笔,否则不给!");
            b.get();
        }
        public synchronized void get() {
            System.out.println("我:得到了本,付出了笔,还是什么都写不了!");
        }
    }
    
    class B {
        public synchronized void say(A a) {
            System.out.println("你说:把你的笔给我,我给你本,否则不给!");
            a.get();
        }
        public synchronized void get() {
            System.out.println("你:得到了笔,付出了本,还是什么都写不了!");
        }
    }
    
    public class TestDemo implements Runnalbe {
        private static A a = new A();
        private static B b = new B();
        public static void main(String args[]) {
            new TestDemo();
        }
        public TestDemo() {
            new Thread(this).start();
            b.say(a);
        }
        public void run() {
            b.say(b);
        }
    }
    

    输出结果

    你说:把你的笔给我,我给你本,否则不给!
    我说:把你的本给我,我给你笔,否则不给!
    

    然后一直在执行,是因为死锁,两个线程都在等待。

    死锁是程序开发之中,由于某种逻辑上的错误所造成的问题,并且不是简单的就会出现的。

    面试题:请解释多个缓和访问一同资源时需要考虑到哪些情况?有可能带来哪些问题?

    • 多个线程访问同一资源时一定要处理好同步,可以使用同步代码块或同步方法来解决。
      • 同步代码块:synchronized(锁定对象) {代码}。
      • 同步方法:public synchronized 返回值 方法名称() {代码}。
    • 但是过多的使用同步,有可能造成死锁。

    总结

    • 简单理解同步和异步操作可以通过synchronized来实现。
    • 死锁是一种不定的状态。

    更多内容戳这里(整理好的各种文集)

    相关文章

      网友评论

        本文标题:Java线程的同步与死锁

        本文链接:https://www.haomeiwen.com/subject/ztlpextx.html