并发编程工具 - juc的atomic工具类、AtomicInteger的项目使用

目录

atomic包的实现原理

atomic包的工具分类

AtomicInteger分析和项目使用


    juc下面的atomic包下面所以的工具类都没有使用AQS,但是却是线程安全的(基于volatile + CAS)。由于没有使用AQS双向队列(底层没有使用LockSupport的park和unpark),则并没有互斥锁,所以可以理解成线程安全的无锁工具类。

atomic包的实现原理

    1)、volatile关键字基于JVM屏蔽底层的内存屏障差异,但都解决了有序性(防止指令重排序)的问题。
    2)、并且Jdk5之后,对volatile关键字进行了增强(volatile修饰的变量 写Hanppen-Before于读取),结合Happen-Before的顺序性和传递性实现了可见性问题。具体可参见:并发理论基础 - JMM模型和Happens-Before规则
    3)、基于无锁的CAS(乐观锁思想),底层会调用Unsafe的相关方法实现了原子性。

至于CAS的ABA问题可以参见:并发编程基础 - synchronized和volatile并发模型对比、从顶层俯瞰juc##四、CAS存在的问题和解决方案

 

atomic包的工具分类

1)、基本数据类型

    AtomicBoolean、AtomicInteger、AtomicLong

2)、数组

    AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

3)、叠加器(没有使用CAS并性能高于基本数据类型)

    DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder

4)、引用类型

    AtomicReference、AtomicStampedReference【使用版本号解决ABA问题】、AtomicMarkableReference【使用Boolean解决ABA问题

5)、对象属性更新器

    AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

 

AtomicInteger分析和项目使用

    

    AtomicInteger的源码结构和方法,直接调用了Unsafe的CAS相关方法:

public class AtomicInteger extends Number implements java.io.Serializable {

    // Unsafe的compareAndSwapInt解决原子性
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // volatile解决可见和有序性
    private volatile int value;
    private static final long valueOffset;

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    static {
        try {
            valueOffset = unsafe.objectFieldOffset(java.util.concurrent.atomic.AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }
}

个人理解,只要是并发的处理Integer等基本数据类型时,就可以使用atomic相关的类,那么在其他优秀的框架中是怎么使用AtomicInteger的呢?

比如tomcat线程池是基于ThreadPoolExecutor进行扩展的,在ThreadPoolExecutorImpl类中就基于AtomicInteger统计了执行的总任务数,基于AtomicLong统计了当前线程最后一次被终止的时间,具体源码如下:

public class ThreadPoolExecutorImpl extends java.util.concurrent.ThreadPoolExecutor {
    /**
     * The number of tasks submitted but not yet finished. This includes tasks
     * in the queue and tasks that have been handed to a worker thread but the
     * latter did not start executing the task yet.
     * This number is always greater or equal to {@link #getActiveCount()}.
     */
    private final AtomicInteger submittedCount = new AtomicInteger(0);
    private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);

    /**
     * Most recent time in ms when a thread decided to kill itself to avoid
     * potential memory leaks. Useful to throttle the rate of renewals of
     * threads.
     */
    private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L);
}

最近自己在写一个报表导出项目(微服务中的一个),导出项目的真正执行的项目(不是微服务,单独部署的Spring boot项目)进行了解耦【为了防止因为导出的异常将整个系统拖垮】。封装的底层使用Http Client线程池,使用Spring Boot的yml(或者properties)中配置的服务列表进行负载均衡调用。调用时可以指定具体的负载均衡策略,其中轮询是少不了的。这时我就使用了 AtomicInteger【ATOMIC_ADDER属性】,如下:

/**
 *  调用 报表执行服务的 服务均衡工具类
 * @author kevin
 * @date 2020/10/23 11:13
 * @since 1.0.0
 */
@Slf4j
@Configuration
@AutoConfigureAfter(ReportExecuteConfigurationProperties.class)
public class ReportExecuteLbs implements ApplicationContextAware, InitializingBean {

    public static ApplicationContext applicationContext;

    public static LbsStrategyEnum strategy = RANDOM;

    /**
     * 多线程使用同一叠加计数器, volatile + CAS处理
     */
    private static final AtomicInteger ATOMIC_ADDER = new AtomicInteger(0);

    /**
     * 组装好请求的uri 列表,允许修改,特别是后续使用 Zookeeper/Nacos等带钩子的配置中心
     */
    private final static CopyOnWriteArrayList<String> uriList = new CopyOnWriteArrayList<>();

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        ReportExecuteLbs.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        ReportExecuteConfigurationProperties properties = applicationContext.getBean(ReportExecuteConfigurationProperties.class);
        List<String> list = properties.getList();
        if (list.isEmpty()) {
//            log.error("获取到的 报表导出执行服务列表为空 !");
            throw new RuntimeException("获取到的 报表导出执行服务列表为空 !");
        }
        if (StringUtil.isBlank(properties.getLoadBalancingStrategy())) {
//            log.info("配置的执行策略为空,使用默认的 随机策略 !");
            strategy = RANDOM;
        } else {
            String loadBalancingStrategy = properties.getLoadBalancingStrategy();
            strategy = LbsStrategyEnum.strategyValue(loadBalancingStrategy);
        }
        list.forEach(info -> uriList.addIfAbsent(properties.getHttpScheme() + info));
    }

    /**
     * 根据配置的策略获取请求的url
     * @return url地址
     */
    public static String getHttp() {
        return getHttp(strategy);
    }

    /**
     *  根据配置的策略获取请求的url
     * @param lbsStrategy 策略字符串
     * @return url地址
     */
    public static String getHttp(String lbsStrategy) {
        LbsStrategyEnum lbsStrategyEnum = LbsStrategyEnum.strategyValue(lbsStrategy);
        if (lbsStrategyEnum == null) {
            return getHttp();
        }
        return getHttp(lbsStrategyEnum);
    }

    /**
     * 根据配置的策略获取请求的url
     * @return url地址
     */
    public static String getHttp(LbsStrategyEnum lbsStrategyEnum) {
        if (uriList.size() == 1) {
            return uriList.get(0);
        }
        if (lbsStrategyEnum == RANDOM) {
            int index = ThreadLocalRandom.current().nextInt(uriList.size());
            return uriList.get(index - 1);
        } else {
            return uriList.get(ATOMIC_ADDER.getAndIncrement() % uriList.size());
        }
    }

}

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值