JAVA多线程基础篇 5、原子性与锁

本文介绍了并发编程中的竞态条件问题,展示了如何通过原子性来解决此类问题。文章详细讲解了悲观锁和乐观锁的概念,包括Java中的synchronized和CAS(Check and Set)算法。悲观锁通过锁定对象确保线程安全,而乐观锁则依赖于CAS操作避免锁竞争。根据场景选择合适的锁策略,可以在保证数据一致性的同时提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 竞态条件

在并发编程中,多线程会抢占运行各自的代码片段,当访问共享数据时会产生不正确的结果。

public class ConditionRaceExp {

    private static volatile int counter = 0;

    public static void main(String[] args) throws InterruptedException {

        Thread[] threads = new Thread[1000];

        for(int i=0;i<1000;i++){
            threads[i] = new Thread(()->{
                for(int k=0;k<10000;k++)
                    counter++;
            });
        }

        for(int i=0;i<1000;i++){
            threads[i].start();
        }

        for(int i=0;i<1000;i++){
            threads[i].join();
        }
        System.out.println(counter);
    }
}

预期结果应该是1000000,但实际运行结果与预期不符。

conditionrace

原因是因为在执行counter++时,等同于counter = counter+1,在多线程环境中这句命令会被打断,比如A线程在执行该语句时,counter瞬时的值为100,A线程执行counter+1,得到值为101,在赋值回counter时,问题发生了——B、C、D线程抢占了CPU资源,并将counter值加到了103,等A线程回到CPU运行的时候,又会把101赋值给counter。 此种情况被成为竞态条件。

2. 原子性

要让程序能得到预期的结果,需要让涉及到竞态条件的程序指令的原子性不被破坏。此时需要将程序进行保护。JAVA提供了锁机制来保护程序,根策略不同可以分为悲观锁乐观锁两种。

2.1 悲观锁

首先需要定义一个对象,锁是上在某个普通对象上的,也可以是某个class对象上。上锁的过程其实就是在对象头上更改状态位、以及写入自己的信息。如果没有指明,则根据方法来判定,如果是实例方法,则锁在this对象上。如果是静态方法,则锁在这个实例所在的类对象上。

Java调用锁有多种方法,一般使用synchronized方法对象。

tips:在发生异常后,线程会自己释放锁的。

Object o = new Object();
synchronized(o){
    //此时,这把锁上在 o 这个对象上
}

synchronized(T.class){
    //此时,是上锁在T.class 这个对象,这个对象是Class类型的,是类加载的虚拟机后创建的。
}

2.2 乐观锁

乐观锁的实现是CAS(Compare and Swap)算法,CAS算法并没有真正“锁定”内存的对象,也被称为无锁算法。
CAS底层原理是,CAS在为对象赋值的时候,会带上对象的预期的值,将预期值和当前值进行比较(Compare),如果是一致的,就进行赋值(Swap)。否则继续重试,因此也称为自旋锁。CAS存在ABA的问题,一般是通过版本号或时间戳加上数据进行双重检验。
CAS在底层是由操作系统保证的,操作系统会调用指令 lock cmpxchg 对缓存行或总线加锁,保证原子性。

2.3 两种锁的比较

尽管悲观锁需要对锁资源进行抢占和排队,但是排队时线程并不占据CPU资源;而乐观锁的线程都会持续自旋(循环轮询)占用CPU的时间片段,因此根据线程并发数和实际测试情况,才能判断那种锁的效率高。

3.加锁的方法

  • 使用synchronized关键字
  • 使用ReentrantLock对象
  • 使用JUC库的各种工具类(详见进阶篇)

总结

锁能保证原子性。但是并不代表上锁就一定安全,程序的设计需要考虑周到。锁分为乐观锁和悲观锁,悲观锁基于对象头锁定,乐观锁基于CAS算法。对于并发数少、执行时间比较短的情况,建议使用CAS;否则建议使用悲观锁。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值