原子类

背景

对于Atomic介绍,网上不缺好的文章,但是都是别人精心咀嚼过的。自己也尝试整理一下,方便今后自己回顾学习。

JDK版本:1.8

简介

谈及原子类,第一感觉就是为了线程安全。线程安全性的三大特性是:原子性、可见性、有序性。

  • 原子性:提供了互斥访问,同一个时刻只能有一个线程来操作;
  • 可见性:一个线程对主内存的修改可以及时被其他线程察觉到;
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

本文就是围绕着原子性展开,首先保证原子性最简单也最常用的就是使用 synchronized关键字,synchronized是基于JVM层级实现的对象锁。synchronized在锁住同步资源的时候,总会认为有其他线程来和他并发操作,所以每次都对同步资源进行锁定,从而来保证线程安全(悲观锁)。直接锁定资源,虽然可以万无一失,但是另一方面的加锁、释放也是需要资源的消耗。

那么有没有一些轻量一点的方式呢?从JDK1.5开始Java提供了Atomic包,这个包中的类提供更加轻量级的方式保证原子性。原子类的实现线程安全的思想是乐观锁。乐观地认为自己对同步资源操作的时候,不会有其他线程干扰,所以不会锁住同步资源。乐观锁的实现一般利用CAS算法实现。

Atomic 简介

Atomic 相关类在 java.util.concurrent.atomic 包中。这些类提供了一种简单、线程安全的更新一个变量的方式。可以分为原子更新基本类型,原子更新数组,原子更新引用,原子更新属性。原子类基本都是使用Unsafe实现的包装类。

原子更新基本类型

  • AtomicBoolean:更新布尔类型
  • AtomicInteger:更新整型
  • AtomicLong:更新长整型

以上的类提供的方法基本一致,下面以 AtomicInteger进行说明,AtomicInteger中常用的方法如下。

  • public final int getAndAdd(int delta) :以为原子方式将输入的delta数值相加,并返回结果
  • public final boolean compareAndSet(int expect, int update):如果输入的值与AtomicInteger实例的当前值相等,更新AtomicInteger实例的值为update的数值。
  • public final int incrementAndGet():自增加1;
  • public final int decrementAndGet() :减1;

实例代码:使用原子类自增一的演示

package com.mark.csdn.concurrent.atomic;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class AtomicIntegerExample {

    private static final int CLIENT_TOTAL = 5000;
    private static  int count = 0;
    private static AtomicInteger atomicIntegerCount = new AtomicInteger(0);
    
    public static void main(String[] args) {
        //使用线程池中的10个线程执行5000次子任务
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < CLIENT_TOTAL; i++) {
            executorService.submit(()->{
                task();
            });
        }
        //关闭线程池
        executorService.shutdown();
        //打印结果
        log.info("普通变量 count的结果是:"+count); //结果小于5000
        log.info("原子类变量 atomicIntegerCount的结果是:"+atomicIntegerCount); //结果为5000
    }

    private static void task() {
        count++;
        atomicIntegerCount.getAndIncrement();
    }
}

正因为 count++操作是线程不安全的(这里涉及JMM中的主内和工作内存的知识点),而AtomicInteger声明的原子整型使得自增加一变得线程安全了。当然也可以通过给task()方法加锁,达到线程安全的目的,但是加锁的范围会更大,而且会带来一些额外的开销。这里只需要保证某一个变量自增线程安全即可,所以用原子类会更加的轻量高效。

原子更新数组

  • AtomicIntegerArray :原子更新整型数组
  • AtomicLongArray:原子更新长整型数组
  • AtomicReferenceArray:原子更新引用数组

常用方法和AtomicInteger类差不多,实例代码:以AtomicIntegerArray为例

package com.mark.csdn.concurrent.atomic;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;

@Slf4j
public class AtomicIntegerArrayExample {

    private static final int CLIENT_TOTAL = 100;
    private static AtomicIntegerArray array = new AtomicIntegerArray(20);
    
    public static void main(String[] args) {
        //使用线程池中的10个线程执行100次子任务
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < CLIENT_TOTAL; i++) {
            executorService.submit(()->{
                task();
            });
        }

        //关闭线程池
        executorService.shutdown();
        
        //这里的循环是为了让线程池中的线程执行完毕
        while (!executorService.isTerminated()) {

        }

        //预期结果,数组中每个元素的值都为100
        for (int i = 0; i < array.length(); i++) {
            if(array.get(i) != CLIENT_TOTAL){
                log.error("发现错误的元素,下标为"+i);
            }
        }
        log.info("运行结束!");
    }

    /**
     * 子任务:每一个线程都对原子数组中的每一个元素都进行加一操作
     */
    private static void task() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndIncrement(i);
        }
    }
}

运行结果:只是打印了“运行结束!”的日志。符合预期结果!

原子更新引用类型

如果更新多个字段,那么可以使用下面的类

  • AtomicReference:原子更新应用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型

原子升级更新引用类型

原子更新Adder累加器

原子更新Accumulator累加器

你可能感兴趣的:(Java多线程与高并发,多线程,java)