Android开发AndroidAndroid开发

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

2019-02-27  本文已影响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很难被正确使用


第八章:异常

上一篇下一篇

猜你喜欢

热点阅读