请解释一下volatile关键字?
volatile是Java中的轻量级的同步机制,总共有三个特性:可见性、非原子性、有序性(也叫禁止重排)
可见性:在Java内存模型中(JMM),多个线程访问主内存中的变量,将变量拷贝到自己的工作内存中。当然线程对变量的操作也算是对副本的操作,当其中一个线程改变了副本就会将改变后副本值更新到主内存中去,加了volatile后其他线程也能知道主内存中数据发生改变,从而自身副本也重新拷贝,反之不不知道已经改变。
非原子性:原子性就是不可分割,该做就一直做。在多线程环境下,多个线程竞争资源,预期值会和实际值有偏差,例如多个线程对主内存中副本数据进行加一操作,A线程进入后还没修改数据就被挂起,这时B进入修改了数据、这时A被唤醒,A中副本数据还没来的及修改就马上执行了+1操作,就造成两个线程执行后数据值一样,造成值覆盖。
有序性:因为很多时候我们写的java代码顺序和虚拟机执行的字节码文件顺序是不一致的,因为JVM会对其进行重优化。但是在高并发环境下这种重优化(重排序)会带来各种问题,单例模式下对象创建过程是:1、分配对象地址 2、初始化对象 3、让对象执向分配的地址 ,当高并发环境下发生的重排序可能会导致 132顺序的发生,从而使其还没有初始化就指向了分配地址,这就造成了判断出错。
AtomicInteger底层原理实现?什么是CAS?
AtomicInteger底层实现:由CAS确保原子性+Valatile
要想弄清楚AtomcInteger怎么实现的还是要先明白CAS。
CAS(Compare and Swap)的意思是比较和交换,使用的就是乐观锁的思想。CAS之所以可以保证原子性,是依赖于他里面的Unsafe类,这个类是在java.sum.muix包下的是native(本地)方法修饰的类,说明他是从娘胎中就由得类、是操作系统底层实现的类,它可以像c语言中的指针一样指向当前对象地址,所以通过Unsafe类的本地方法获取到对象的地址 ,然后通过本类对象+本类对象地址确定本类对象的值,然后再进行判断预期值和实际对象的值是否相等,如果相等就表明没有被修改,如果不相等就表明被修改了,就重新获取值再比较。
所以搞清楚了CAS不难搞懂AtomicInteger:附上源码及解释
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提
供“比较并替换”的作⽤)
private static final Unsafe unsafe = Unsafe.getUnsafe();
//偏移量,就是所谓的地址
private static final long valueOffset;
static {
try {
//通过unsafe本地方法获取到地址
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//这里的value被指定为了volatile,所以在被修改后有可见性,value用于后面的比较并替换
private volatile int value;
当然你会想说为什么我不用Synchronized而用这个CAS呢,因为Synchronized每次都只会让一个线程进入所以会导致系统的并发性下降、所以这里用到了CAS。当然CAS也会存在一些弊端:1、只允许一个对象的并发操作,如果是对个对象时就会无法保证原子性。2、多次循环空转浪费CPU内存 。3、会发生ABA问题。
ABA问题:当一个线程A执行速度更快,线程B执行速度更慢。A线程修改了多次主线程副本值,但是最终结果还是原来的值,B线程操作时,那本身副本值与预期值对比,发现一样是true执行,并不知道A线程已经多次修改了该值。这种问题就是ABA问题。
解决方案:使用原子引用阀值。通过原子引用阀值添加一个类似版本号信息的值,如果线程修改了一次该值就加一,不管最后修改值是否与原来一致。后面判断版本号与自己版本号是否一致即可。
解释一下为什么ArrayList、Map、Set都是线程不安全的(并发)?
因为在多线程环境下,多个线程竞争资源(例如操作数+1)时,可能随时会被其他线程抢去,这样在高并发环境之下很容易抛出并发修改异常(ConcurrentModificationException)。
解决方案:1、加Synchronized锁 2、使用Collections.SynchronizedList(new xxx); 3、使用写时复制(CopyOnWriterArrayXXx)、HashMap使用(CurrentHashMap)
12原理都是控制线程数。
3、CopyOnWriterArrayXXX:是给操作对象创建一个副本,当一个线程在进行读操作时,另一个想要进行写操作,那么就创建一个一模一样的对象供这个线程写、当写完后把地址引用指向修改后的副本,依次类推。
CurrentHashMap则是分段管理。