一,什么是原子操作?如何实现原子操作
1,synchronized可以完成原子操作,他是给予阻塞的锁的机制,但是有问题:
如果被阻塞的线程优先级很高怎么办?
拿到锁的线程一直不释放锁怎么办?
有大量线程进行竞争,消耗cpu。还容易出现死锁
锁的粒度比较大,影响性能。
二,CAS的原理(Compare And Swap:比较交换)
1,从指令级别保证这是一个原子操作。
每个CAS都包含三个运算符:
一个内存地址V
一个期望的值A
一个新值B
基本思路:
如果在内存地址V上进行操作,如果说这个地址上存放的值就是我期望的值A。就给地址V赋给新值B.
如果内存地址上不是我期待的A值,那就什么都不做。
在循环(这里用的是死循环,不带条件的一直在那里循环。又叫:自旋)里不断的进行CAS操作
利用了现代操作系统都支持CAS的指令,循环这个指令,直到成功为止。
其实是不再语言层面进行处理,而是把它交给了cpu和内存去实现,利用cpu的多处理能力,在硬件层面实现多线程安全。
三,CAS的问题
1,ABA问题
问题描述:
第一个线程拿到内存地址上的值。此时第二个线程也拿到内存地址上的值,然后修改成B,然后又改回去。
在第二个线程完成前面一顿操作之后,第一个线程才开始比较内存地址上的是否和A相等。那么比较的结果
肯定是相同的啦。第二个线程,修改过值,又修改回去。但是对于第二个线程的那些操作,第一个线程却不知道,这是有风险的。
怎么解决:引入版本号,每次变化,版本号都变化。这样每次数据变化都可以感知到
2,开销问题
在死循环里不断的进行操作,如果这个操作长期不成功,浪费cpu资源
3,只能保证一个共享变量的原子操作
一个内存地址只能指向一个变量,当有多个共享变量时,就无法保证操作的原子性。
那么如果有多个共享变量呢?我们可以把多个共享变量变成一个共享变量,比如把多个变量封装到一个类中。
四,JDK中相关原子操作类的使用
1,更新基本类型:
AtomicBoolean,AtomicInteger,AtomicLong
2,更新数组类:
AtomicInterArray,AtomicLongArray,AtomicReferenceArray
3,更新引用类型:
AtomicReference,AtomicMarkableReference,AtomicStampedReference
4,原子更新字段类:
AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
五,实际使用
1,AtomicIntegerArray
public class UseAtomicArray { static int[] value = new int[]{1,2}; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0,3); //上面的操作只能修改引用对象的值 System.out.println(ai.get(0)); //实际的对象没有被改变 System.out.println(value[0]); } }
2,AtomicReference
/** * 引用类型的原子操作类 */ public class UseAtomicReference { static AtomicReference<UserInfo> userRef = new AtomicReference<>(); public static void main(String[] args) { UserInfo userInfo = new UserInfo("Mark",12); userRef.set(userInfo); UserInfo udpateUser = new UserInfo("Bill",14); userRef.compareAndSet(userInfo,udpateUser); //引用的对象被修改了 System.out.println(userRef.get().getName()+":"+userRef.get().getAge()); //原对象的值并没有改变 System.out.println(userInfo.getName()+":"+userInfo.getAge()); } static class UserInfo{ private String name; private int age; public UserInfo(String name,int age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
3,AtomicStampedReference
/** * 带版本戳的原子操作类 */ public class UseAtomicstampedReference { static AtomicStampedReference<String> asr = new AtomicStampedReference<>("mark",0); public static void main(String[] args) throws InterruptedException { final int oldStamp = asr.getStamp(); final String oldReference = asr.getReference(); //获取初始值和初始版本号 System.out.println(oldReference+"--"+oldStamp); Thread rightStampThread = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"当前变量值:"+oldReference+ "当前版本戳:"+oldStamp+"-"+asr.compareAndSet(oldReference, oldReference+"+Java",oldStamp,oldStamp+1)); } }); Thread errorStampThread = new Thread(new Runnable() { @Override public void run() { String reference = asr.getReference();//重新获取reference int stamp = asr.getStamp(); System.out.println(Thread.currentThread().getName()+"当前变量值:"+reference+ "当前版本戳:"+stamp+"-"+asr.compareAndSet(reference, reference+"+C",oldStamp,oldStamp+1)); } }); rightStampThread.start(); //确保让rightStampThread先执行 rightStampThread.join(); //第二次修改会失败,因为上面的线程已经修改过一次了,再次在旧的版本号上修改是无法成功的 errorStampThread.start(); errorStampThread.join(); System.out.println("finally--->"+asr.getReference()+"-"+asr.getStamp()); } }