java 线程安全解决方案-android 线程阻塞怎么解决
JUC原子类位于java.util.concurrent.atomic包中,基于CAS轻量级原子操作,使得程序运行效率高于使用synchronized关键字。
原子类分类
根据操作的目标数据类型,JUC包中的原子类可以分为四类:
基本原子类线程安全原理
基础原子类(以AtomicInteger为例)主要通过CAS自旋+volatile的组合实现线程安全,既保证了变量操作的线程安全,又避免了synchronized重量级锁的高开销,使得执行Java程序效率大大提高。 其中,CAS用于保证变量操作的原子性,volatile关键字用于保证变量的可见性。
基本原子类中的主要方法都是通过CAS自旋实现的:
CAS自旋的主要操作:如果一次CAS失败,获取最新值后java 线程安全解决方案,再次进行CAS操作,直到成功。
volatile关键字修饰变量:保证任何线程都能随时获取变量的最新值java 线程安全解决方案,其目的是保证变量值的线程可见性。
基本数据操作的原子性
在多线程环境下,如果涉及基本数据类型(int、long)的并发操作,不建议使用synchronized重量级锁进行线程同步。 建议优先使用基础原子类,保证并发操作的线程安全。 注意:基础原子类只能保证对一个变量的原子操作。
基本原子类使用合成实例
public class AtomicTest { /* * 使用10个线程分别操作某个变量自增1000次,预期得到的结果为10*1000=10000 */ @Test public void testAtomicInteger() throws InterruptedException { AtomicInteger amount = new AtomicInteger(0); CountDownLatch latch = new CountDownLatch(10); //创建10个线程 for(int i = 0; i < 10; i++) { //每个线程执行1000次自增 ThreadUtil.getMixedTargetThreadPool().submit(() -> { for(int j = 0; j < 1000; j++) { amount.getAndIncrement(); } //每个线程执行完一次后,倒数闩减少1次 latch.countDown(); }); } //等待10条线程全部执行完毕 latch.await(); Print.tco("10条线程分别自增1000次结果: " + amount.get()); } }
数组原子类使用示例
public class AtomicTest { @Test public void testAtomicIntegerArray() { int[] array = {0,1,2,3,4,5,6,7,8,9}; AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(array); //获取第5个元素并设置为20 atomicIntegerArray.getAndSet(4, 20);//{0,1,2,3,20,5,6,7,8,9} Print.tco("数组第5个元素为: " + atomicIntegerArray.get(4));//20 //获取第2个元素并自增 atomicIntegerArray.getAndIncrement(1);//{0,2,2,3,20,5,6,7,8,9} Print.tco("数组第2个元素为: " + atomicIntegerArray.get(1));//2 //获取第8个元素并加6 atomicIntegerArray.getAndAdd(7, 6);//{0,2,2,3,20,5,6,13,8,9} Print.tco("数组第8个元素为: " + atomicIntegerArray.get(7));//13 //比较并设置第1个元素为44 boolean result = atomicIntegerArray.compareAndSet(0, 0, 44); Print.tco("设置数组第一个元素结果: " + result);//{44,2,2,3,20,5,6,13,8,9} } }
对象数据操作的原子性
基本原子类型只能保证一个变量的原子操作。 如果需要操作多个变量,CAS不能保证原子操作。 如果需要对 POJO 执行原子操作怎么办? 这时候可以使用AtomicReference来保证对象引用的原子性。
引用类型原子类:
public V getReference():获取封装后的数据
public int getStamp():获取封装数据的版本戳
上面三个类提供的方法几乎是一样的。 以AtomicReference为例说明用法
一、定义一个POJO对象
public class Cat implements Serializable { public String uid; public String nickName; public volatile int age; public Cat(String uid, String nickName) { this.uid = uid; this.nickName = nickName; } @Override public String toString() { return "Cat{" + "uid='" + uid + '\'' + ", nickName='" + nickName + '\'' + '}'; } }
使用 AtomicReference 以原子方式修改 Cat 引用
public class AtomicTest { @Test public void testAtomicReference () { AtomicReferencecatRef = new AtomicReference<>(); Cat cat = new Cat("1", "薛大米"); //使用原子引用包装POJO catRef.set(user); Print.tco("before cas, catRef is " + catRef.get()); Cat updateCat = new Cat("2", "董菠萝"); boolean success = catRef.compareAndSet(cat, updateCat); Print.tco("cas result is " + success); Print.tco("after cas, catRef is " + catRef.get()); } }
阐明:
POJO对象用原子引用类型包装后,只能保证POJO引用的原子操作。 修改被包装的POJO对象的字段值时不能保证原子性。 其实质就是比较两个对象的内存地址。
属性更新原子类
如果需要保证更新对象某个字段(或属性)的原子性,需要使用原子类进行属性更新
以上三个类提供的方法基本相同。 以 AtomicIntegerFieldUpdater 为例说明其用法:
首先,必须保证更新的对象属性必须使用public volatile修饰符;
其次,由于对象属性修改类型原子类是一个抽象类,所以每次使用都必须使用静态方法newUpdater()创建更新器,并且需要设置要更新的类和属性。
@Test public void testAtomicIntegerFieldUpdater() { //创建属性更新器,对User类的age属性进行原子性修改 AtomicIntegerFieldUpdaterupdater = AtomicIntegerFieldUpdater.newUpdater(Cat.class, "age"); Cat cat = new Cat("1", "薛大米"); Print.tco(updater.getAndIncrement(cat));//返回原值:0,自增后age为1 Print.tco(updater.getAndAdd(cat, 3));//返回原值1,增加3后age为4 Print.tco(updater.get(user));//返回原值 4 }