【第75题】JAVA高级技术-多线程9(i++的原子性问题)

举报
小虚竹 发表于 2022/06/04 23:40:28 2022/06/04
【摘要】 回城传送–》《JAVA筑基100例》 文章目录 零、前言一、题目描述二、解题思路三、代码详解多学一个知识点 四、推荐专栏五、示例源码下载 零、前言 ​ 今天是学习 JAVA语言 打卡的...

回城传送–》《JAVA筑基100例》

零、前言

​ 今天是学习 JAVA语言 打卡的第75天,每天我会提供一篇文章供群成员阅读( 不需要订阅付钱 ),读完文章之后,按解题思路,自己再实现一遍。在小虚竹JAVA社区 中对应的 【打卡贴】打卡,今天的任务就算完成了。

​ 因为大家都在一起学习同一篇文章,所以有什么问题都可以在群里问,群里的小伙伴可以迅速地帮到你,一个人可以走得很快,一群人可以走得很远,有一起学习交流的战友,是多么幸运的事情。

​ 学完后,自己写篇学习报告的博客,可以发布到小虚竹JAVA社区 ,供学弟学妹们参考。

​ 我的学习策略很简单,题海策略+ 费曼学习法。如果能把这100题都认认真真自己实现一遍,那意味着 JAVA语言 已经筑基成功了。后面的进阶学习,可以继续跟着我,一起走向架构师之路。

一、题目描述

题目:重现i++的原子性问题,并解决原子性问题

二、解题思路

并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。

可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性:程序执行的顺序按照代码的先后顺序执行。

我们先来说i++的原子性问题

/**
 * i++原子性问题:
 * eg: int i=10;
 * i=i++;//?会等于多少
 * i++操作步骤:
 * int temp =i;
 * i = i +1;
 * return temp;
 * 所以i++分为三步:“读--》改--》写”
 * <p>
 * 说到i++,肯定要顺便说下++i,++i的操作步骤:
 * int i=10;
 * return i=i+1;
 **/

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

由上面的例子可知道,i++分为三步:“读–》改–》写”,这个过程就会给并发线程带来风险。

使用Atomic解决原子性问题:

在jdk1.5以后java.util.concurrent.atomic包下,提供了

在这里插入图片描述

大量的原子变量,它们内部使用CAS算法。

三、代码详解

重现i++的原子性问题

package com.xiaoxuzhu;

import org.junit.Test;

/**
 * Description: 原子性 原子变量
 * 
 * i++原子性问题:
 * eg: int i=10;
 * i=i++;//?会等于多少
 * i++操作步骤:
 * int temp =i;
 * i = i +1;
 * return temp;
 * 所以i++分为三步:“读--》改--》写”
 * <p>
 * 说到i++,肯定要顺便说下++i,++i的操作步骤:
 * int i=10;
 * return i=i+1;
 * 
 * @author xiaoxuzhu
 * @version 1.0
 *
 * <pre>
 * 修改记录:
 * 修改后版本	        修改人		修改日期			修改内容
 * 2022/5/14.1	    xiaoxuzhu		2022/5/14		    Create
 * </pre>
 * @date 2022/5/14
 */
public class TestAtomicDemo {

    @Test
    public void iAtomic() {
        int i = 10;
        i = i++;
        System.out.println("i=" + i);
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        for (int i = 0; i < 10; i++) {
            new Thread(myThread).start();
        }
    }

    static class MyThread implements Runnable {
        private int serialNumber = 0;

        public void run() {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSerialNumber());
        }

        public int getSerialNumber() {
            return serialNumber++;
        }

    }
}




  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

但不是每次都会出现,可以多试几次

如图

使用Atomic解决原子性问题

package com.xiaoxuzhu;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;


/**
 * Description:原子性 原子变量 增加Atomic解决原子性问题
 * 
 * i++原子性问题:
 * eg: int i=10;
 * i=i++;//?会等于多少
 * i++操作步骤:
 * int temp =i;
 * i = i +1;
 * return temp;
 * 所以i++分为三步:“读--》改--》写”
 * <p>
 * 说到i++,肯定要顺便说下++i,++i的操作步骤:
 * int i=10;
 * return i=i+1;
 * 
 * @author xiaoxuzhu
 * @version 1.0
 *
 * <pre>
 * 修改记录:
 * 修改后版本	        修改人		修改日期			修改内容
 * 2022/5/14.1	    xiaoxuzhu		2022/5/14		    Create
 * </pre>
 * @date 2022/5/14
 */
public class TestAtomicDemo1 {

    @Test
    public void iAtomic() {
        int i = 10;
        i = i++;
        System.out.println("i=" + i);
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        for (int i = 0; i < 10; i++) {
            new Thread(myThread).start();
        }
    }

    static class MyThread implements Runnable {
        private AtomicInteger serialNumber = new AtomicInteger();

        public void run() {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSerialNumber());
        }

        public int getSerialNumber() {
            return serialNumber.getAndIncrement();
        }

    }
}




  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

如图

多学一个知识点

CAS算法

CAS(Compare-And-Swap)是一种硬件对并发的支持,针对多处理器操作而设计的,处理器中的一种特殊指令,用于管理对共享数据的并发访问。

CAS是一种无锁的非阻塞算法实现,是硬件对于并发操作的支持,保证了数据变量的原子性。

Cas包含了3个操作数:

  1. 内存值 V
  2. 预估值 A
  3. 更新值 B

当且仅当 V == A 时, V = B; 否则,不会执行任何操作。

简单的来说,CAS有3个操作数,要读写的内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V(不做任何操作,然后重新获取主存V值,重新操作。)。

这是一种 乐观锁 的思路,它相信在它修改之前,没有其它线程去修改它。

package com.xiaoxuzhu;


/**
 * Description:CAS算法 简单模拟
 * 
 * 1、内存值 V
 * 2、预估值 A
 * 3、更新值 B
 * 
 * @author xiaoxuzhu
 * @version 1.0
 *
 * <pre>
 * 修改记录:
 * 修改后版本	        修改人		修改日期			修改内容
 * 2022/5/14.1	    xiaoxuzhu		2022/5/14		    Create
 * </pre>
 * @date 2022/5/14
 */
public class CASDemo {

    public static void main(String[] args) {
        final CAS cas = new CAS();

        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int expectedValue = cas.getValue();
                    boolean b = cas.compareAndSet(expectedValue, (int)(Math.random()*100));
                    System.out.println(b);
                }
            }).start();
        }
    }

    static class CAS {
        //内存值
        private volatile int value = 0;

        //获取返回内存值 获取值一定要加同步锁
        private synchronized int getValue(){
            return value;
        }

        /**
         * 如果预估值与原来的值一直,则修改内存为新的值,否则,不做处理。 无论是否修改,都返回原来的内存值。
         * 这是用到的是乐观锁
         **/
        public synchronized int compareAndSwap(int expectedValue, int newValue) {
            int oldValue = value;
            System.out.println("old:" + oldValue + ",expectedValue:" + expectedValue + ",newValue:" + newValue);
            if (expectedValue == oldValue) {
                value = newValue;
            }

            return oldValue;
        }

        // 如果更新成功,舊的內內存值和預估值相等。
        public synchronized boolean compareAndSet(int expectedValue, int newValue) {
            return expectedValue == compareAndSwap(expectedValue, newValue);
        }
    }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

如图

四、推荐专栏

《JAVA从零到壹》

《JAVA筑基100例》

五、示例源码下载

关注下面的公众号,回复筑基+题目号

筑基75

文章来源: xiaoxuzhu.blog.csdn.net,作者:小虚竹,版权归原作者所有,如需转载,请联系作者。

原文链接:xiaoxuzhu.blog.csdn.net/article/details/125027219

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。