美文网首页Android开发AndroidAndroid开发
Android基础进阶之EffectiveJava翻译系列(第九

Android基础进阶之EffectiveJava翻译系列(第九

作者: 青楼爱小生 | 来源:发表于2019-02-27 19:38 被阅读4次

    ​ 线程允许多个活动页面同时执行.并发编程比单线程难,因为很多事情一起处理容易出错,也很难减少错误,但是你不能避免并发.这章帮助你编写简洁的,正确的,良好阅读性的并发编程

    Item66 同步共享的可变数据

    ​ synchronized 关键字可以保证一次只有一个线程访问代码块,许多开发者认为同步就是一种互斥,防止对象在另一个线程修改时处于不一致的状态.在这种观点中,对象处于一种正确的状态,因为访问它的方法锁住了.这些方法确保对象的状态由一种状态安全的转移到另一种状态.

    ​ 这种观点只正确了一半,不同步的话,一个线程的改变对其它线程是不可见的.通过相同的锁,同步不仅阻止线程在不一致状态下观察对象,而且确保每个进入同步方法或块的线程都能看到所有一致性的效果.

    ​ 考虑一下从一个线程停止另一个线程,Java lib提供了Thread.stop方法,但是这个方法被遗弃了,因为它是不安全的---将导致数据损坏.一种建议方法是获取到第一个线程的boolean变量,一些人可能会这么写:

    //bad
    public class StopThread {
    private static boolean stopRequested;
    public static void main(String[] args)
        throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
                }
            });
            backgroundThread.start();
            TimeUnit.SECONDS.sleep(1);
            stopRequested = true;
            }
    }
    

    ​ 你可能期望这个程序运行大约一秒,然后主线程设置stopRequested为true,从而导致后台线程的循环终止 .然而在我的机器上,程序永远不会停:子线程永远在循环!

    ​ 问题在于,在没有同步的情况下,无法保证后台线程何时会看到主进程所做的修改. 在没有同步的情况下,虚拟机转换成以下代码:

    while (!done)
        i++;
    //转换
    if (!done)
        while (true)
            i++;
    

    修复方式如下:

    //good
    public class StopThread {
        private static boolean stopRequested;
        private static synchronized void requestStop() {
            stopRequested = true;
        }
        private static synchronized boolean stopRequested() {
            return stopRequested;
        }
    public static void main(String[] args)
        throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                public void run() {
                    int i = 0;
                    while (!stopRequested())
                        i++;
                }
            });
            backgroundThread.start();
            TimeUnit.SECONDS.sleep(1);
            requestStop();
        }
    } 
    

    注意:读和写都是同步的,光对写方法同步,同步会失效

    还可以使用volatile关键字修复为:

    public class StopThread {
        private static volatile boolean stopRequested;
        public static void main(String[] args)
            throws InterruptedException {
                Thread backgroundThread = new Thread(new Runnable() {
                    public void run() {
                        int i = 0;
                        while (!stopRequested)
                            i++;
                    }
                });
                backgroundThread.start();
                TimeUnit.SECONDS.sleep(1);
                stopRequested = true;
        }
    }
    

    但使用volatile关键字要小心,考虑如下代码

    //bad
    private static volatile int nextSerialNumber = 0;
    public static int generateSerialNumber() {
        return nextSerialNumber++;
    } 
    

    乍看之下没有什么问题,但是"++"操作不是原子性的,包含了两个操作,一个是读旧值,另一个是在旧值的基础上加一,在赋值.修复方式为加上synchronized关键字:

    //good
    private static volatile int nextSerialNumber = 0;
    public static synchronized int generateSerialNumber() {
        return nextSerialNumber++;
    } 
    

    ​ 避免此类问题最好的方式是不要共享可变数据.要么共享不可变的数据,要么就不共享.换句话说,在一个线程中定义可变数据.如果采用此策略,则必须将其文档化,以便程序维护此原则

    总之,当多个线程共享数据时,读取或写入数据的每个线程都必须执行同步.没有同步,无法保证一个线程的修改对另一个线程可见.这将会导致程序安全问题,而且很难调试.如果你只需要内部间的线程通信而不考虑互斥, volatile 关键字可以替代synchronized,但是volatile很难被正确使用


    第八章:异常

    相关文章

      网友评论

        本文标题:Android基础进阶之EffectiveJava翻译系列(第九

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