深入理解Volatile,synchronized,原子操作的必要性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012070360/article/details/60323286

多线程问题

为什么要使用Volatile?

为什么要使用Volatile?搞清楚这个问题先看一个情形。

如果线程 A 对对象中的某个变量进行修改后还没来得及写回主存,线程 B 也对该变量进行了修改,那最后刷新回主内存后的值一定和期望的值不一致。这就是因为为了让线程 A 对变量做的修改线程 B 立即可以看到,我们可以使用 volatile 修饰变量或者对修改操作使用同步。其实这个情况volatile用于写的话效果不是很好,因为它仅仅对读可见性比较好,但是大多数情况同步代价太高,基本上volatile可以解决这个问题。
假如,如果线程 A 对对象中的某个变量进行修改后还没来得及写回主存,线程 B 想及时的观察这个数据的修改后的信息,这就是因为为了让线程 A 对变量做的修改线程 B 立即可以看到,我们可以使用 volatile 修饰变量或者对修改操作使用同步。

竞态条件触发?

当多个线程操作同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。

在临界区中使用适当的同步就可以避免竞态条件,比如 synchronized, 显式锁和原子操作类等。

内存可见性?

内存可见性”问题。
为了让线程 A 对变量做的修改线程 B 立即可以看到,我们可以使用 volatile 修饰变量或者对修改操作使用同步。
关于读,存储的变量使用volatile关键字,可以不加锁的情况下解决内存可见性的问题

多线程中堆,栈的分布

多线程中堆,栈的分布
堆为同一个 JVM 中所有线程共享,存放运行时创建的对象和数组数据;
栈为每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中、
这里写图片描述

java中的内存分配

Java程序运行时的内存结构分成:方法区、栈内存、堆内存、本地方法栈几种。
方法区存放装载的类数据信息,包括:
·基本信息:每个类的全限定名、每个类的直接超类的全限定名、该类是类还是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表。
·每个已装载类的详细信息:运行时常量池、字段信息、方法信息、静态变量、到类classloader的引用、到类class的引用。
栈内存
Java栈内存由局部变量区、操作数栈、帧数据区组成,以帧的形式存放本地方法的调用状态(包括方法调用的参数、局部变量、中间结果……)。
堆内存
堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
本地方法栈内存
Java通过Java本地接口JNI(Java Native Interface)来调用其它语言编写的程序,在Java里面用native修饰符来描述一个方法是本地方法。
String的内存分配
String是一个特殊的包装类数据,由于String类的值不可变性,当String变量需要经常变换其值时,应该考虑使用StringBuffer或StringBuilder类,以提高程序效率。

一个锁引发的问题

看代码

public boolean waitDone() {
    final Object waitDoneLock = new Object();
    final Runnable unlockRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (waitDoneLock) {
                waitDoneLock.notifyAll();
            }
        }
    };

    synchronized (waitDoneLock) {
        mHandle.post(unlockRunnable);
        try {
            waitDoneLock.wait();
        } catch (InterruptedException ex) {
            Log.v(TAG, "waitDone interrupted");
            return false;
        }
    }
    return true;
}

执行的时候报anr,就是因为主线程wait时间过久。其实在handler中处理的notifiall是没用的
wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。这时候handler由于之前锁被拿走了一直处于停滞状态。。。现在拿到了锁以后就可以执行结束,但是由于handler也是在主线程中,也是停滞的,如果handler是子线程的话,wait释放了锁,handler里就可以执行了,然后notify唤醒主线程,这个问题就没事了
这时候我们解决的方法就是在anr阀值内把它notify一下
妥协的解决办法之一

public boolean waitDone() {
    final Object waitDoneLock = new Object();
    final Runnable unlockRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (waitDoneLock) {
                waitDoneLock.notifyAll();
            }
        }
    };
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (waitDoneLock) {
                waitDoneLock.notifyAll();
            }

        }
    }).start();
    synchronized (waitDoneLock) {
        mHandle.post(unlockRunnable);
        try {
            waitDoneLock.wait();
        } catch (InterruptedException ex) {
            Log.v(TAG, "waitDone interrupted");
            return false;
        }
    }
    return true;
}

猜你喜欢

转载自blog.csdn.net/u012070360/article/details/60323286