侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Java并发编程之原子操作类AtomicInteger详解

孔子说JAVA
2022-06-03 / 0 评论 / 0 点赞 / 102 阅读 / 8,279 字 / 正在检测是否收录...

并发编程原子类完整教程:Java并发编程之原子操作类实战教程

在JDK1.5之前,Java中要进行业务并发时,不管什么情况下,都需要由程序员独立完成代码实现,此时在进行一些并发设计时需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担。在JDK1.5出现之后,java.util.concurrent工具包作为简化并发出现。

AtomicInteger,是java.util.concurrent.atomic包下的一个类,是一个提供原子操作的Integer的类。在Java语言中,i和i操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口来实现。AtomicInteger不能当作Integer来使用。

1、AtomicInteger作用及使用场景

1.1 AtomicInteger作用

  1. 支持原子操作的Integer类。

  2. 主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。

1.2 AtomicInteger使用场景

在实际工作中,我们需要使用AtomicInteger的两种情况:

  1. 作为多个线程同时使用的原子计数器。

  2. 在比较和交换操作中实现非阻塞算法。

2、为什么需要AtomicInteger原子操作类

对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。具体看下面的例子。

2.1 计数器例子

public class AtomicIntegerTest {
 
    private static final int THREADS_CONUT = 20;
    public static int count = 0;
 
    public static void increase() {
        count++;
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

本例中我们运行了20个线程,每个线程都会对count变量进行1000次自增操作,如果上面这段代码能够正常并发的话,我们的期望结果应该是20000才对。但是我们运行程序之后会发现每次运行的实际结果都不相同,都是一个小于20000的数字。我们可以思考一下原因。

2.2 计数器例子改进1 - 使用volatile

我们改进一下上面的代码,使用 volatile 修饰count变量,这里的 volatile 关键字很2个重要的特性:

  • 保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—“先行发生原则”);
  • 禁止指令的重排序优化;

使用volatile修饰count变量代码如下:

public class AtomicIntegerTest {
    private static final int THREADS_CONUT = 20;
    public static volatile int count = 0;
 
    public static void increase() {
        count++;
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

我们再次运行下代码,测试结果还是和上面的一致,每次依然是输出小于20000的数字。这又是为什么呢?

  • 这并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。

2.3 计数器例子改进2 - 使用AtomicInteger

改进例子1中,实际结果与我们期望值不一致的原因主要在于java的自增运算不是原子操作,所以我们继续改进一下上面的代码,用 AtomicInteger 原子类型。

import java.util.concurrent.atomic.AtomicInteger;
 
public class AtomicIntegerTest {
 
    private static final int THREADS_CONUT = 20;
    public static AtomicInteger count = new AtomicInteger(0);
 
    public static void increase() {
        count.incrementAndGet();
    }
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_CONUT];
        for (int i = 0; i < THREADS_CONUT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
 
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

再次运行代码后可以发现,结果每次都输出20000,得到了我们期望的正确结果,这要归功于 AtomicInteger.incrementAndGet() 方法的原子性。

3、非阻塞同步

同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。

阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?

在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 “操作和冲突检测” 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。

我们来看AtomicInteger.incrementAndGet()方法,它的实现也比较简单

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
*/
public final int incrementAndGet() {
   for (;;) {
     int current = get();
     int next = current + 1;
     if (compareAndSet(current, next))
      return next;
   }
}

incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:

    /**
     * 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 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);
    }

可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。

4、AtomicInteger的常用的方法

4.1 get()方法

get()方法:获取当前value的值,无锁

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //1、value.get()获取当前value的值,无锁
        AtomicInteger get = new AtomicInteger();
        System.out.println("value:" + get.get());
        get = new AtomicInteger(10);
        System.out.println("value:" + get.get());
    }
}

4.2 set()方法

set()方法:设置指定值,无锁,多线程使用会出问题,一般用于初始化数值。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //2、value.set(value)是设置值,无锁,多线程使用会出问题,一般用于初始化数值
        AtomicInteger set = new AtomicInteger();
        set.set(50);
        System.out.println("value:" + set.get());
    }
}

4.3 getAndSet()方法

getAndSet()方法:获取旧的值(当前值),重新设置新的值

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //3、获取旧的值(当前值),重新设置新的值
        AtomicInteger getAndSet = new AtomicInteger();
        int oldValue = getAndSet.getAndSet(80);
        int newValue = getAndSet.get();
        System.out.println("旧值:" + oldValue);
        System.out.println("新值:" + newValue);
    }
}

4.4 compareAndSet()方法

compareAndSet()方法:如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。这里需要注意的是这个方法的返回值实际上是是否成功修改,而与之前的值无关

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //4、如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。这里需要注意的是这个方法的返回值实际上是是否成功修改,而与之前的值无关
        AtomicInteger compareAndSet = new AtomicInteger(10);
        boolean result = compareAndSet.compareAndSet(10,30);
        System.out.println("result:" + result);
        System.out.println("value:" + compareAndSet.get());
    }
}

4.5 getAndIncrement()方法

getAndIncrement()方法:先获取当前值再进行自增

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //5、先获取当前值再进行自增
        AtomicInteger getAndIncrement = new AtomicInteger(10);
        int newValue1 = getAndIncrement.getAndIncrement();
        System.out.println("newValue1:" + newValue1);
        System.out.println("value:" + getAndIncrement.get());
    }
}

4.6 getAndDecrement()方法

getAndDecrement()方法:先获取当前值再进行自减

import java.util.concurrent.atomic.AtomicInteger;


public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //6、先获取当前值再进行自减
        AtomicInteger getAndDecrement = new AtomicInteger(10);
        int newValue2 = getAndDecrement.getAndDecrement();
        System.out.println("newValue2:" + newValue2);
        System.out.println("value:" + getAndDecrement.get());
    }
}

4.7 getAndAdd()方法

getAndAdd()方法:先获取当前值再加上指定值

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //7、先获取当前值再加上指定值
        AtomicInteger getAndAdd = new AtomicInteger(10);
        int newValue3 = getAndAdd.getAndAdd(5);
        System.out.println("newValue3:" + newValue3);
        System.out.println("value:" + getAndAdd.get());
    }
}

4.8 incrementAndGet()方法

incrementAndGet()方法:先自增再获取当前值

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //8、先自增再获取当前值
        AtomicInteger incrementAndGet = new AtomicInteger(10);
        int newValue4 = incrementAndGet.incrementAndGet();
        System.out.println("newValue4:" + newValue4);
        System.out.println("value:" + incrementAndGet.get());
    }
}

4.9 decrementAndGet()方法

decrementAndGet()方法:先自减再获取当前值

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //9、先自减再获取当前值
        AtomicInteger decrementAndGet = new AtomicInteger(10);
        int newValue5 = decrementAndGet.decrementAndGet();
        System.out.println("newValue5:" + newValue5);
        System.out.println("value:" + decrementAndGet.get());
    }
}

4.10 addAndGet()方法

addAndGet()方法:先加上指定值再获取当前值

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDetailTest {
    /**
     * AtomicInteger特点:
     * 1、保证可见性
     * 2、保证有序性
     * 3、保证原子性
     */

    public static void main(String[] args) {
        //10、先加上指定值再获取当前值
        AtomicInteger addAndGet = new AtomicInteger(10);
        int newValue6 = addAndGet.addAndGet(5);
        System.out.println("newValue6:" + newValue6);
        System.out.println("value:" + addAndGet.get());
    }
}
0

评论区