java并发原子类AtomicBoolean解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 如果看过我之前的文章都知道这几天一直在更新java多线程这块的知识点,因为这块的知识点确实是比较多而且也别繁杂,因此对于java多线程基础知识点也会在两个多月的时间全部写完,这篇文章主要是针对java并发包下的一个原子类AtomicBoolean的讲解。

一、为什么使用AtomicBoolean?



我们平时一般都是使用的boolean来表示布尔变量,但是在多线程情况下boolean是非线程安全的。为什么是非线程安全的呢?我们看下面的这个例子:

private volatile Boolean flag = false;
publich void test() {
  synchronized(flag) {
    //去做其他的事:访问临界资源
    flag = !flag;
  }
}

大家可以看到,这个操作好像并没有什么问题,我们使用了synchronized关键字对flag对象进行上锁,这时候同一时刻就只能有一个线程去运行test方法中的代码了。如果你这样想那就大错特错了,其实此时synchronized对这块资源不起任何作用。为什么不起作用呢?我们来分析一下:


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


如何去解决这个问题呢?这时候我们的AtomicBoolean类就可以出马了,他可以很好的去解决这个问题。下面我们就来好好地分析一下AtomicBoolean类吧。


二、AtomicBoolean的使用


在一开始我们曾经也说到,在单线程中我们使用boolean是完全没有问题的,我们看如下代码:

public class Test6 implements Runnable{
    public static boolean flag = true;
    private String name;
    public Test6(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        if(flag) {
             System.out.println(name + ",起床");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",上班");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",下班");
            flag = !flag;
        }else {
             System.out.println(name + "想进来却进不来");
        }
    }
}

上面的代码功能是这样的,起床上班下班这三件事,一个人做完另外一个才可以继续做。这种boolean情况,在单线程状态下是安全的,但是在多线程条件下就是非线程安全的。我们可以创建两个线程去测试一下:

v2-d828133b50addde53c5dce9a7767d91a_1440w.jpg

原本我们想的是起床上班下班这三件事,一个人完成另外一个人再做,但是通过运行结果我们会发现,并列执行了。怎么才能实现我们的功能呢?我们再看下面的代码:

public class Test6 implements Runnable{
     private static AtomicBoolean flag = new AtomicBoolean(false);
     private String name;
    public Test6(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        if(flag.compareAndSet(false, true)) {
             System.out.println(name + ",起床");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",上班");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",下班");
            flag.set(false);
        }else {
             System.out.println(name + "想进来却进不来");
        }
    }
}

此时我们换成AtomicBoolean,在运行一下看看:

v2-fc0e35d390c52244097e0ab05a20cad4_1440w.jpg

我们会看到,此时执行的顺序就确定了张无忌想进来却进不来了。这就是其基本使用。下面我们分析一下其原理。


三、源码分析


想要了解其原理我们就必须要到源码中去看。在上面我们使用了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 {@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;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }


这个compareAndSet源码里面调用了unsafe的compareAndSwapInt方法,也就是使用了CAS机制,举一个我之前举的例子,这里expect和update是什么意思呢?也就是说我们现在的boolean如果不是except那就不更新,如果是我们预期的except,那就更新,更新的值就是update。也就是CAS原理,我们通过例子解释一下:


要给儿子订婚,你预期的儿媳妇是西施,但是儿子找的女朋友是貂蝉,你一看不是你预期的西施(except),一气之下就什么也不做,如果是预期的西施,那就给他们订婚。


注意:在这里我们还会发现一个问题,那就是我们的Boolean其实转化成了int类型,1表示true 0表示false。


这就是compareAndSet实现,底层使用的是CAS机制。当然还有很多其他的方法,我们可以看一下:

//返回当前值
public final boolean get() {
    return value != 0;
}
//先返回旧值,再设置新值
public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        prev = get();
    } while (!compareAndSet(prev, newValue));
    return prev;
}
//设置新值
public final void set(boolean newValue) {
    value = newValue ? 1 : 0;
}
//设置新值,该操作会让Java插入Store内存屏障,避免发生写操作重排序
public final void lazySet(boolean newValue) {
    int v = newValue ? 1 : 0;
    unsafe.putOrderedInt(this, valueOffset, v);
}

对于AtomicBoolean类其实是非常简单的。也是java并发机制中比较简单的类。这篇文章就先到这。如有问题还请指正。

目录
打赏
0
0
0
0
26
分享
相关文章
|
4天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
21 5
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
56 15
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
20 0
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
77 6
【潜意识Java】期末考试可能考的高质量大题及答案解析
Java 期末考试大题整理:设计一个学生信息管理系统,涵盖面向对象编程、集合类、文件操作、异常处理和多线程等知识点。系统功能包括添加、查询、删除、显示所有学生信息、按成绩排序及文件存储。通过本题,考生可以巩固 Java 基础知识并掌握综合应用技能。代码解析详细,适合复习备考。
29 4
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
30 0
|
11天前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
125 60
【Java并发】【线程池】带你从0-1入门线程池
|
7天前
|
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
55 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
90 14

热门文章

最新文章

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等