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

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

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

目 录CONTENT

文章目录

Java并发编程之原子操作类AtomicBoolean实战详解

孔子说JAVA
2022-06-05 / 0 评论 / 0 点赞 / 105 阅读 / 7,772 字 / 正在检测是否收录...

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

与 AtomicInteger 非常类似,AtomicBoolean 提供了一种原子性地读写布尔类型变量的解决方案,AtomicBoolean 所提供的原子性方法在使用习惯上也与AtomicInteger非常一致,通常情况下,该类将被用于原子性地更新状态标识位。AtomicLong 可以理解是加了 synchronized 的 long。该类是 java.util.concurrent.atomic 包下的,从JDK1.5开始提供。

1、boolean 和 AtomicBoolean

在java中使⽤ boolean 来表⽰布尔变量,但在多线程情况下 boolean 是⾮线程安全的。我们可以看个例⼦:

public class AtomicBooleanTest {

    private static volatile Boolean flag = true;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                if (flag) {
                    try {
                        Thread.sleep(1);
                        flag = false;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " modify the flag. ");
                }

            }).start();
        }
    }
}

在单线程模式下上面的代码是没有问题的,但是如果在多线程环境中(在高并发条件下),如果都需要对flag进行修改,就会破坏其原子性。会得到实际结果与期望结果不一致的问题。

可以用 synchronized 关键字改造上面的代码

public class AtomicBooleanTest {

    private static volatile Boolean flag = true;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
               synchronized(flag) {
                if (flag) {
                    try {
                        Thread.sleep(1);
                        flag = false;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " modify the flag. ");
                }
              }
            }).start();
        }
    }
}

这个操作粗看好像没有什么问题,我们使⽤了 synchronized 关键字对 flag 对象进⾏上锁,这样同⼀时刻就只会有⼀个线程去运⾏ test ⽅法中的代码了。如果你这样想那就⼤错特错了,其实此时synchronized 对这块资源不起任何作⽤,为什么不起作⽤呢?我们来分析⼀下:

  • 对于对象flag来说主要有两个值 true 和 false,但是 true 和 false 却是两个不同的常量对象,也就是说 synchronized 关键字其实锁住的只是 false 对象,当下⾯test⽅法中把 flag 改为 true 就表⽰了另外⼀个对象,这就是为什么 synchronized 关键字失效的原因。

那么我们该如何去解决这个问题呢?这时候 AtomicBoolean 类就可以出马了,它可以很好地去解决这个问题。

public class AtomicBooleanTest {

    private static AtomicBoolean flag = new AtomicBoolean(true);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                if (flag.compareAndSet(true,false)) {
                    try {
                        Thread.sleep(1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " modify the flag. ");
                }

            }).start();
        }
    }
}

通过上面的代码改造后,就可以很好的解决多线程下 flag 变量访问的问题。AtomicBoolean 使用了原子类型,利用的是CAS机制,可以有效的避免多个线程间破坏原子性。

本质上:我们看源码可以发现,该类型采用的依然是int类型表示 true 和 false。本文最后我们会对 AtomicBoolean 源码进行解析。

public AtomicBoolean(boolean initialValue) {
    value = initialValue ? 1 : 0;
}

2、AtomicBoolean的基本用法

AtomicBoolean提供的方法比较少也比较简单,本教程中只对其做简单的介绍,其基本原理与AtomicInteger极为类似。

2.1 AtomicBoolean的创建

// AtomicBoolean 无参构造: 此时默认值为 false, 等价于 AtomicBoolean(false)
AtomicBoolean ab = new AtomicBoolean();

// AtomicBoolean 有参构造,
AtomicBoolean ab = new AtomicBoolean(false);

2.2 AtomicBoolean值的更新

/**
   对比并且设置boolean最新的值
   其中expect代表当前的AtomicBoolean数值,update则是需要设置的新值,
   该方法会返回一个boolean的结果:当expect和AtomicBoolean的当前值不相等时,修改会失败,返回值为false;
   若修改成功则会返回true。
 **/
  public final boolean compareAndSet(boolean expect, boolean update);
  
 // 同上
 public final boolean weakCompareAndSet(boolean expect, boolean update)
 
 // 设置AtomicBoolean最新的value值,该新值的更新对其他线程立即可见。
 set(boolean newValue)
 
 // 设置AtomicBoolean的布尔值
 lazySet(boolean newValue)
 
 // 返回AtomicBoolean的前一个布尔值,并且设置新的值。
 getAndSet(boolean newValue);

示例:

 public static void main(String[] args) {
     AtomicBoolean atomicBoolean = new AtomicBoolean(false);
     boolean res = atomicBoolean.compareAndSet(false, true);
     System.out.println(res); // true,更新成功
     System.out.println(atomicBoolean.get()); // 更新为true了

     res = atomicBoolean.compareAndSet(false, false);
     System.out.println(res); // fasle,更新失败
     System.out.println(atomicBoolean.get()); // 所以还是true
     
     //如果想让某种操作只执行一次,初始atomicBoolean为false
     AtomicBoolean atomicBoolean = new AtomicBoolean(false);
     //如果当前值为false,设置当前值为true,如果设置成功,返回true
     if (atomicBoolean.compareAndSet(false, true)){
       //执行操作
     }
 }

2.3 使用场景

在很多场景中,都需要用到加载资源等初始化操作,而且只需要初始化一次,这时候就可以使用 AtomicBoolean 方便的处理。

  • 判断为初次操作
  • 并发处理下,保证只初始化一次,不会重复初始化

3、AtomicBoolean内幕(源码解读)

AtomicBoolean的实现方式比较类似于AtomicInteger类,实际上AtomicBoolean内部的value本身就是一个volatile关键字修饰的int类型的成员属性,我们来看一下它的源码:

package java.util.concurrent.atomic;
import sun.misc.Unsafe;

public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    /**
     * *使用给定的初始值创建一个新的{@code AtomicBoolean}。
     */
    public AtomicBoolean(boolean initialValue) {
        value = initialValue ? 1 : 0;
    }

    /**
     * 使用给定的初始值创建一个新的{@code AtomicBoolean}。
     */
    public AtomicBoolean() {
    }

    /**
     * Returns the current value.
     *
     * @return the current value
     */
    public final boolean get() {
        return value != 0;
    }

    /**
     * 如果当前值{@code ==}是期望值,则以原子方式将该值设置为给定的更新值。
     */
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

    /**
     * 如果当前值{@code ==}是期望值,则以原子方式将该值设置为给定的更新值。
     * weakCompareAndSet可能会虚假地失败,并且不提供排序保证,因此,不是{@code compareAndSet}的合适的替代方法。
     */
    public boolean weakCompareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

    /**
     * 无条件设置为给定值。
     */
    public final void set(boolean newValue) {
        value = newValue ? 1 : 0;
    }

    /**
     * 最终设置为给定值。
     */
    public final void lazySet(boolean newValue) {
        int v = newValue ? 1 : 0;
        unsafe.putOrderedInt(this, valueOffset, v);
    }

    /**
     * 以原子方式设置为给定值并返回上一个值。
     */
    public final boolean getAndSet(boolean newValue) {
        boolean prev;
        do {
            prev = get();
        } while (!compareAndSet(prev, newValue));
        return prev;
    }

    /**
     * Returns the String representation of the current value.
     * @return the String representation of the current value
     */
    public String toString() {
        return Boolean.toString(get());
    }

}

3.1 AtomicBoolean类的内部属性

 // setup to use Unsafe.compareAndSwapInt for updates
// 设置为使用Unsafe.compareAndSwapInt进行更新
private static final Unsafe unsafe = Unsafe.getUnsafe();

// 保存修改变量的实际内存地址,通过unsafe.objectFieldOffset读取
// valueOffset将用于存放value的内存地址偏移量
private static final long valueOffset;

// 初始化的时候,执行静态代码块,计算出保存的value的内存地址便于直接进行内存操作
// objectFieldOffset(Final f):返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。

static {
  try {
     // 获取value的内存地址偏移量
     valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) {
    throw new Error(ex); 
  }
}

private volatile int value;

3.2 AtomicBoolean类的构造函数

/** 
 * Creates a new {@code AtomicBoolean} with the given initial value.
 * 通过给定的初始值,将boolean转为int后初始化value
 * @param initialValue 初始值
 */
public AtomicBoolean(boolean initialValue) { 
   value = initialValue ? 1 : 0;
}


/**
 * Creates a new {@code AtomicBoolean} with initial value {@code false}. 
 * 初始化为默认值,默认为false,因为int的默认值是0
 */
public AtomicBoolean() {}

3.3 AtomicBoolean类的get方法

/** 
 * 返回当前值 
 *
 * @return 返回当前值 
 */
public final boolean get() {
  return value != 0;
}

/** 
 * Atomically sets to the given value and returns the previous value. 
 * 通过原子的方式设置给定的值,并返回之前的值
 * 
 * @param newValue 新值
 * @return 返回之前的值
 */
public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        //先get()到原来的值,再进行原子更新,会一直循环直到更新成功
        prev = get(); 
    } while (!compareAndSet(prev, newValue));
    return prev;
}
  • 注意:getAndSet方法中使用了 compareAndSet(boolean expect, boolean update) 来设置值,我们跟踪进去看一下这个方法的源码:
/**
 * 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(boolean expect, boolean 
update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    //unsafe.compareAndSwapInt:原子性地更新偏移地址为valueOffset的属性值为u,当且仅当偏移地址为alueOffset的属性的当前值为e才会更新成功,否则返回false。
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
  • unsafe.compareAndSwapInt: 原子性地更新偏移地址为valueOffset的属性值为u,当且仅当偏移地址为alueOffset的属性的当前值为e才会更新成功,否则返回false。

3.4 AtomicBoolean类的set方法

/**
 * Unconditionally sets to the given value. 无条件设置为给定值
 *
 * @param newValue the new value (新值)
 */
public final void set(boolean newValue) {
    value = newValue ? 1 : 0;
}

/**
 * Eventually sets to the given value. 最终设置为给定值
 * 
 * @param newValue the new value  (新值)
 * @since 1.6 
 */
public final void lazySet(boolean newValue) {
    int v = newValue ? 1 : 0;
    //unsafe.putOrderedInt(this, valueOffset, v):根据偏移地址,设置对应的值为指定值v。这是一个有序或者有延迟的putIntVolatile()方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。
    unsafe.putOrderedInt(this, valueOffset, v);
}
  • unsafe.putOrderedInt(this, valueOffset, v): 根据偏移地址,设置对应的值为指定值v。这是一个有序或者有延迟的putIntVolatile()方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。

这个类实现原子性操作的主要核心代码都是在Unsafe类里面。有兴趣的话,可以把这个类的源码拉来看看。

0

评论区