跟我学(Effective Java 2)第66条:同步访问共享的可变数据

第66条:同步访问共享的可变数据

关键字synchronized可以保证同一时刻,只有一个线程可以执行某个方法,或者摸一个代码块。许多人把同步的概念理解为一种互斥的方式,即,当一个对象被一个线程修改的时候,可以阻止另一个线程观察到对象内部不一致的状态。这种观点对象创建的时候处于一致的状态,当有方法访问它的时候,它就被锁定了。

这种观点是正确的,但是它并不是同步的全部意义。同步不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护的之前所有的修改效果。

“为了提高性能,在读写原子数据的时候,应该避免使用同步。”这个建议是非常危险而且错误的。 因为,虽然读写原子数据都是原子操作,但是不保证一个线程的写入的值对于另一个线程是完全可见的。为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的。

如果对共享的可变数据的访问不能同步,其后果将非常可怕,即使这个变量是原子可读写的。要阻止一个线程妨碍另一个线程,建议做法是让第一个线程轮询(poll)一个boolean域,这个域一开始为false,但可以通过第二个线程设置为true,以表示第一个线程将终止自己。因为boolean域是原子的,所以保证程序在访问这个域时不会使用同步。

public class Thread {
    private static boolean stopRequest;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0 ;
               /* while (!stopRequest){
                    i++;
                    //System.out.println(i); 如果注掉 循环不会关闭 线程一直运行。 如果打开注释则 会关闭。考虑system源码是否有同步功能 源码中确实有同

步功能 使得线程可以正常关闭。源码中得到验证system确实有同步锁。
                }*/
                while (!stopRequest){
                    i++;
                }
            }
        });
        thread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequest = true;
        System.out.println("Thread over.");
    }

    //需要把requestStop 和stopRequest同步化。如果读和写操作没有被同步,同步就不会起作用。
    private static synchronized void requestStop() {
        stopRequest = true;
    }

    private static synchronized boolean stopRequest() {
        return stopRequest;

    }

    //由于++运算符不是原子的,因此在多线程的时候会出错。++运算符执行两项操作:1、读取值;2、写回新值(相当于原值+1)。
    private static volatile int nextSerialNumber = 0;

    public static int generateSerialNumber() {
        return nextSerialNumber++;
    }
}

让一个线程在短时间内修改一个数据对象,然后与其他线程共享,这是可以接受的,只同步共享对象引起的动作。然后其他线程没有进一步的同步也可以读取对象。只要它没有再被修改。这种对象被称作事实上不可变的(effectively immutable)。将这种对象引用从一个线程传递到其他的线程被称作安全发布(safe publication)。 安全发布对象引用有许多种方法:可以将它保存在静态域中,作为类初始化的一部分;可以将它保存在volatile域、final域或者通过正常锁定访问的域中,或者可以将它放到并发的集合中。

总之,当多个线程共享可变数据的时候,每个读或写数据的线程都必须执行同步。如果没有同步,就无法保证一个线程所做的修改可以被另一个线程获知。未能同步共享可变数据会造成程序的活性失败(liveness failure)和安全性失败(safety failure)。 这样的失败时最难调试的。他们可能是间歇性的,且与时间相关,程序的行为在不同的VM可能根本不同,如果只需要线程之间的交互通信,而不需要互斥,volatile修饰符就是一种可以接受的同步形式,但要正确的使用它可能需要一些技巧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值