当前位置: 主页 > JAVA语言

java 线程安全解决方案-android 线程阻塞怎么解决

发布时间:2023-02-08 14:34   浏览次数:次   作者:佚名

JUC原子类位于java.util.concurrent.atomic包中,基于CAS轻量级原子操作,使得程序运行效率高于使用synchronized关键字。

原子类分类

根据操作的目标数据类型,JUC包中的原子类可以分为四类:

解决线程死锁_android 线程阻塞怎么解决_java 线程安全解决方案

基本原子类线程安全原理

基础原子类(以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   
}