原子性 以及悲观锁, 乐观锁

文章详细解释了原子性的概念,通过并发编程的例子展示了非原子性操作可能导致的问题。原子性是保证数据一致性的重要因素,可以通过加锁(如Java的synchronized)来实现。同时,文章介绍了悲观锁和乐观锁,悲观锁假设多线程操作会被打断,通常采用立即加锁的方式;而乐观锁则假设操作不会被打断,使用版本号检查来避免冲突。最后,讨论了JVM中的锁实现,包括重量级锁和轻量级锁。
摘要由CSDN通过智能技术生成

1. 前言

今天这篇文章要详细的说说,什么叫原子性,以及如果不是原子性的话,怎么能保证原子性。

2. 原子性

  1. 先说下并发编程的三大特性:可见性, 有序性, 原子性。 无论是在什么语言,原子性都是非常重要的,既然是这么晦涩的概念,那什么是原子性呢???
  2. 先来讲述下概念:一组动作,要么都成功,要么都失败,其目的是为了保证数据的一致性。 这个怎么理解呢??? 比如:银行要将用户A的钱转给用户B. 如果A的钱扣除成功了,但是用户B的钱转账失败了,会怎么办呢,是不是数据跟我们预想的就不一致了. 如果还是不能理解的话,可以想想Java开发时的Mysql事务,其实也是一个道理。

2.1 实例讲述

    private static int count = 0;
    private static Object o = new Object();

    public static void test01() throws InterruptedException {
        int MAX_COUNT = 100;
        Thread[] threads = new Thread[MAX_COUNT];
        CountDownLatch countDownLatch = new CountDownLatch(MAX_COUNT);

        for (int i = 0; i < MAX_COUNT; i++) {
            threads[i] = new Thread(() -> {
//                synchronized (o) {
                    for (int j = 0; j < 100; j++) {
                        count ++;
                    }
                    countDownLatch.countDown();
//                }
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }

        countDownLatch.await();
        System.out.println(count);
    }

大家可以看看上面的实例,结果是不是每次都是10000. 其实结果不是的,为什么会出现这种情况呢???

其实是并发线程导致的,大家可以看看上述计数的位置n ++. 千万不要以Java的角度来看代码,因为Java的代码计算机不识别的。 看如下图:

在这里插入图片描述
简简单单的一句话i ++被翻译为汇编语言后,就这么多句话。而运算语句i ++. 可以简简单单分为三句话取值 => 运算 => 设置值

既然是分为多行语句,所以运行到任何一句语句中都可能被别的线程截断的。

换言之,整个i ++ 是不能作为一个整体而运行的。这就是不能保证所谓的原子性。

那我们最终的目的就很简单了,就是保证执行过程的原子性,如何保证原子性呢??? 对了, 上锁

2.1.1 新的实例

大家还是看不懂上述的实例的话,给大家看下加锁以及不加锁的编译字节码

  • 加锁前的字节码
public class T01_Thread_Test01 {
    static int i = 0;
    public static void main(String[] args) {
        i ++;
    }
}

在这里插入图片描述

  • 加锁后的字节码
public class T01_Thread_Test01 {
    static int i = 0;
    public static void main(String[] args) {
        synchronized (T01_Thread_Test01.class) {
            i ++;
        }
    }
}

在这里插入图片描述

其实通过上述的实例就会发现,中间部分多了monitorenter 。其实就是用来加锁的。加锁的目的就是为了多线程的序列化

2.2 锁的本质

这里先揭晓答案:锁的本质是:并发编程的序列化。 那什么叫并发编程的。其实大家可以理解为100+人同时上厕所,这样大家都很快能解决完,但是估计会很挤,而且从道德上而言就不允许这样。

那我们怎么办呢??? 就是通过维持秩序来一个一个上厕所。这样其实也是可以都上厕所的,但是就是整个过程会变慢了。 其实这就是锁的本质。

在这里插入图片描述

3. 乐观锁 以及悲观锁

多线程访问数据 ==> 竞争条件 ==> 导致数据不一致, 怎么办呢??? ==> 线程同步(将线程的执行顺序安排好) ==> 加锁

3.1 悲观锁

  1. 悲观的认为:一个操作的执行一定会被别的线程打断的,所以一般我们的上锁 都是悲观锁。一言不合就上锁
  2. 直接上锁,并发线程对于同一把锁 保持同步

3.2 乐观锁

  1. 乐观的认为:某一个操作是不会被别的线程打断的。(乐观锁 又称 是自旋锁 以及无锁
  2. 既然是无锁是怎么保证执行的顺序的呢??? 添加version + 每次计算后对比version + 如果version不一致的话,重新进行计算

3.3 JVM中两种锁

  • 重量级锁(经过操作系统的调度)synchronized早期都是这种锁(目前的实现中升级到最后也是这种锁)
  • 轻量级锁(CAS的实现,不经过OS调度)(无锁 - 自旋锁 - 乐观锁)

4. 总结

今天的目的是:更多的以具体实例来详解说明了原子性。 同时也是普及了乐观锁 以及悲观锁的概念。如果大家觉得有什么不足的地方,欢迎评论区及时留言交流啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值