| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> PHP知识库 -> Java并发基础(一):多个线程读写同一共享变量是否存在并发问题? -> 正文阅读 |
|
[PHP知识库]Java并发基础(一):多个线程读写同一共享变量是否存在并发问题? |
目录 4.5、Synchronized/ReentrantLock加锁 一、经典面试题有没有在JAVA笔试或面试中遇见过这样的题目:统计服务器某个接口的访问次数。
问题:多线程调用access()方法时,接口访问次数统计的结果是否能保证准确呢? 自定义线程中的实例变量针对其他线程有共享和不共享之分。 1.1、多个线程共享同一个变量,产生线程安全问题。
2)、不共享数据时每个线程都拥有自己作用域的变量,且多个线程之间相同变量名的值也不相同
二、内存模型概念????????我们的程序是运行在Java虚拟机上面的,Java虚拟机本身有自己的内存模型, Java的内存模型和计算机的CPU内存模型又有很多相同之处。 2.1、CPU内存模型????????计算机在执行程序的时候,每条指令都是在CPU中执行的,而在CPU执行指令的过程中会涉及到数据的读取和写入操作,而在计算机运行过程中所有的数据都是存放在主存中的(比如一台普通的4C8G机器,这个8G就是指主存的容量),CPU则是从主存中读取数据进行运行。 ????????由于CPU执行速度非常快,比计算机主存的读取和写入的速度快了很多,这样就会导致CPU的执行速度大大下降。 ????????因此,每个CPU都会自带一个高速缓冲区,在运行的时候,会将需要运行的数据从计算机主存先复制到CPU的高速缓冲区中,然后CPU再基于高速缓冲区的数据进行运算,运算结束之后,再将高速缓冲区的数据刷新到主存中。这样CPU的执行指令的速度就可以大大提升。 2.2、JVM内存模型????????JVM启动之后,操作系统会为JVM进程分配一定的内存空间,这部分内存空间就称为“主内存”。另外Java程序的所有工作都由线程来完成,而每个线程都会有一小块内存,称为“工作内存”即线程栈。?Java中的线程在执行的过程中,会先将数据从主内存(指堆内存)中复制到线程的工作内存,然后再执行计算,执行计算之后,再把计算结果刷新到“主内存”中。 三、基于内存模型一步一步分析缓存一致性问题????????假设现在有两个线程同时访问了这个接口的access()方法,两个线程都执行了accessCount++,在内存中是怎么样执行的呢? 首先我们要明白,计算机需要执行accessCount++ 这个语句,需要分为以下3个步骤:
先来看看第1步,假设两个线程同时来执行accessCount()方法 上面图中在第1个步骤的时候,线程1和线程2都会把accessCount的值从主存中复制到线程所属的工作内存中,两个线程此时得到的accessCount的值都是0。 接着两个线程执行第2步操作:将accessCount的值进行加1: 图中,线程1和线程2都进行了第2步的计算,然后线程1得到的结果是 accessCount=1,线程2得到的结果也是accessCount=1。 接着两个线程都到了第三步:将accessCount的值写回主存中: 线程1和线程2都计算完之后就会将计算结果刷新回主存,特别注意一下图中红框的内容,这是两个线程把计算结果刷新回主存的步骤,两个红框中操作的执行顺序不分先后(在实际运行情况,两个操作的顺序是随机的,可能是线程1先刷新,也可能是线程2先刷新),但是这不影响结果,因为无论是线程1还是线程2,写回主存的结果都是accessCount=1。 但是实际上,我们观察到是2个线程都执行了一次access()方法,按照预期来说accessCount的值应该是等于2才对。 这种多个线程访问同一个对象时,调用这个对象的方法得到不正确的结果,这种问题称为线程安全问题。 四、解决并发问题的方法有哪些?解决并发问题方法分为两类:无锁和有锁。 无锁分为:局部变量、不可变对象、ThreadLocal、CAS原子类。 有锁分为:synchronized关键字 和 reentranLock可重入锁。 4.1、局部变量善用局部变量可以避免出现线程安全问题,因为局部变量仅仅存在于每个线程的工作内存中。
只有当每个线程都执行到int i =0时,会在各自线程栈中创建该变量。 4.2、不可变对象不可变对象是指一经创建,则对外的状态就不会改变的对象。如果一个对象的状态不变,无落多少个线程,对其如何操作,都不改变。例如字符串对象就是不可变对象。String s = "a",指字面值a是不可变的,而引用s可以变。 4.3、ThreadLocalThreadLocal本质是在每个线程有自己的一个副本,每个线程的副本互不影响。 ?一个命令为“I”的ThreadLocal类,他会在每个线程都有一个Integer对象,虽然每个线程都会在主内存把Integer对象拷贝到工作内存,但是拷贝的不是一个对象。 4.4、CAS原子类CAS(Compare And Swap)比较交换。 对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS比较与交换的伪代码可以表示为:
?其中内存值V是56,旧的预期值CPU1和CPU2中的值,要修改的新值为最新值。 ? ? ? ? 在Java中,以Atomic为前缀的一系列类都采用CAS思想。 Atomic使用的是无锁的CAS操作,基于乐观锁,并发性能比较高,可以多个线程同时执行,保证线程安全。 Atomic简单使用:首先声明一个AtomicInteger的成员变量,然后再atomicAdd()方法中调用incrementAndGet()。
atomicInterger源码分析:内部有一个Unsafe实例,Unsafe类提供硬件级别的原子操作,因为Java无法直接访问到操作系统底层的硬件,故Java使用native方法进行扩展,其中Unsafe类就是一个操作入口。Unsafe提供几种功能:CAS操作、内存的分配和释放、挂起和恢复线程、定位对象字段内存地址、修改对象的字段值等。 其中对于Unsafe类的CAS的操作,主要调用了unsafe的getAndInt()方法: ?首先通过var5获取旧值,然后调用compareAndSwapInt()通过CAS操作对数据比较交换,如果操作失败进行while循环直到成功。 4.5、Synchronized/ReentrantLock加锁Synchronized和ReentrantLock都采用悲观锁策略。 Synchronized是语言层面 ReentrantLock是编程方式实现
加锁原理:首先两个线程争抢同一把锁,假如线程1获取到锁,而线程二没获取锁就会进入等待队列,等到线程1执行完代码逻辑,会去通知线程2,此时线程2重新尝试获取锁,假如线程2获取成功,则执行代码。 ? |
|
PHP知识库 最新文章 |
Laravel 下实现 Google 2fa 验证 |
UUCTF WP |
DASCTF10月 web |
XAMPP任意命令执行提升权限漏洞(CVE-2020- |
[GYCTF2020]Easyphp |
iwebsec靶场 代码执行关卡通关笔记 |
多个线程同步执行,多个线程依次执行,多个 |
php 没事记录下常用方法 (TP5.1) |
php之jwt |
2021-09-18 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/29 4:01:20- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |