shiro如何保证session不失效_请问在不加锁的情况下如何保证线程安全?

概念

compare and swap,解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

简单点来说就是修改之前先做一下对比,校验数据是否被其他线程修改过,如果修改过了,那么将内存中新的值取出在与内存中的进行对比,直到相等,然后再做修改。

假如我们要对变量:num做累加操作,num初始值=0。1.cpu前往内存取出num;2.判断内存中的num是否被修改;3.做+1操作;4.将修改后的值写入内存中;

993ad0922f115e5967a5ab8c9ab2b8be.png

这时候可能会有疑问了,判断、自加、写回内存难道不会发生线程安全问题吗?既然cas能成为并发编程中安全问题的解决这,那么这个问题肯定是不会发生的,为什么呢?因为判断、自加、写回内存这是一个由硬件保证的原子操作,硬件是如何保证原子性的,请先看下面这个例子

需求:

使用三个线程分别对某个成员变量累加10W,打印累加结果。

我们使用两种方法完成此需求。1.正常累加(既不加锁,也不使用原子类)。1.使用synchronized。
2.使用原子类(Atomic)。

实现

1.正常累加(既不加锁,也不使用原子类)。

这种方式没有什么说的,直接上代码

package com.ymy.test;public class CASTest {        private static long count = 0;    /**     * 累加10w     */    private static  void add(){        for (int i = 0; i< 100000; ++i){            count+=1;        }    }    public static void main(String[] args) throws InterruptedException {        //开启三个线程   t1   t2    t3        Thread t1 = new Thread(() ->{            add();        });        Thread t2 = new Thread(() ->{            add();        });        Thread t3 = new Thread(() ->{            add();        });        long starTime = System.currentTimeMillis();        //启动三个线程        t1.start();        t2.start();        t3.start();        //让线程同步        t1.join();        t2.join();        t3.join();        long endTime = System.currentTimeMillis();        System.out.println("累加完成,count:"+count);        System.out.println("耗时:"+(endTime - starTime)+" ms");    }}

执行结果

2a2c121a05b5c62f33a0509459f5d869.png

很明显,三个线程累加,由于cpu缓存的存在,导致结果远远小于30w,这个也是我们预期到的,所以才会出现后面两种解决方案。

2.使用synchronized

使用synchronized时需要注意,需求要求我们三个线程分别累加10W,所以synchronized锁定的内容就非常重要了,要么直接锁定类,要么三个线程使用同一把锁,关于synchronized的介绍以及锁定内容请参考:java并发编程之synchronized

第一种,直接锁定类,我这里采用锁定静态方法。

我们来改动一下代码,将add方法加上synchronized关键字即可,由于add方法已经时静态方法了,所以现在锁定的时整个CASTest类。

 /**     * 累加10w     */    private static synchronized   void add(){        for (int i = 0; i< 100000; ++i){            count+=1;        }    }

运行结果第一次:

efdc48ce2e82e4cc9bf299622985f3c3.png

第二次:

470e4d3b606efeb549493bb1afb4f282.png

第三次:

6d7589290a0850fd55a0b5b85a0fb6d3.png

这里就有意思了,加了锁的运行时间居然比不加锁的运行时间还少?是不是觉得有点不可思议了?其实这个也不难理解,这里就要牵扯到cpu缓存以及缓存与内存的回写机制了,感兴趣的小伙伴可以自行百度,今天的重点不在这里。

第二种:三个线程使用同一把锁

改造代码,去掉add方法的synchronized关键字,将synchronized写在add方法内,新建一把钥匙(成员变量:lock),让三个累加都累加操作使用这把钥匙,代码如下:

package com.ymy.test;public class CASTest {    private static long count = 0;    private static final String lock = "lock";    /**     * 累加10w     */    private static  void add() {        synchronized(lock){            for (int i = 0; i < 100000; ++i) {                count += 1;            }        }    }    public static void main(String[] args) throws InterruptedException {        //开启三个线程   t1   t2    t3        Thread t1 = new Thread(() -> {            add();        });        Thread t2 = new Thread(() -> {            add();        });        Thread t3 = new Thread(() -> {            add();        });        long starTime = System.currentTimeMillis();        //启动三个线程        t1.start();        t2.start();        t3.start();        //让线程同步        t1.join();        t2.join();        t3.join();        long endTime = System.currentTimeMillis();        System.out.println("累加完成,count:" + count);        System.out.println("耗时:" + (endTime - starTime) + " ms");    }}

结果如下:

ac4e640239a468157f5d22bcbe4c7818.png

这两种加锁方式都能保证线程的安全,但是这里你需要注意一点,如果是在方法上加synchronized而不加static关键字的话,必须要保证多个线程共用这一个对象,否者加锁无效。

原子类

原子类工具有很多,我们举例的累加操作只用到其中的一种,我们一起看看java提供的原子工具有哪些:

17b21288947a8c84a1033bdaaff0ca3b.png

工具类还是很丰富的,我们结合需求来讲解一下其中的一种,我们使用:AtomicLong。

AtomicLong提供了两个构造函数:

9d36b26aec6fc5de00a234451d71ac06.png
7db0d9a02d07a50e9dcc748b65d07bf5.png

value:原子操作的初始值,调用无参构造value=0;调用有参构造value=指定值

其中value还是被volatile 关键字修饰,volatile可以保证变量的可见性,什么叫可见性?可见性有一条很重要的规则:Happens-Before 规则,意思:前面一个操作的结果对后续操作是可见的,线程1对变量A的修改其他线程立马可以看到,具体请自行百度。

我们接着来看累加的需求,AtomicLong提供了一个incrementAndGet(),源码如下:

/**     * Atomically increments by one the current value.     *     * @return the updated value     */    public final long incrementAndGet() {        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;    }

Atomically increments by one the current value :原子的增加一个当前值。好了,我们现在试着将互斥锁修改成原子类工具,改造代码:1.实例化一个Long类型的原子类工具;
2.
for循环中使用incrementAndGet()方法进行累加操作。

改造后的代码:

package com.ymy.test;import java.util.concurrent.atomic.AtomicLong;public class CASTest {//    private static long count = 0;    private static final String lock = "lock";    private static AtomicLong atomicLong = new AtomicLong();    /**     * 累加10w     */    private static void add() {        for (int i = 0; i < 100000; ++i) {            atomicLong.incrementAndGet();        }    }    public static void main(String[] args) throws InterruptedException {        //开启三个线程   t1   t2    t3        Thread t1 = new Thread(() -> {            add();        });        Thread t2 = new Thread(() -> {            add();        });        Thread t3 = new Thread(() -> {            add();        });        long starTime = System.currentTimeMillis();        //启动三个线程        t1.start();        t2.start();        t3.start();        //让线程同步        t1.join();        t2.join();        t3.join();        long endTime = System.currentTimeMillis();        //System.out.println("累加完成,count:" + count);        System.out.println("累加完成,count:" + atomicLong);        System.out.println("耗时:" + (endTime - starTime) + " ms");    }}

结果:

11b2b4060a89f5d4a075e75f711e2afa.png

可以得到累加的结果也是:30w,但时间却比互斥锁要久,这是为什么呢?我们一起来解剖一下源码。

AtomicLong incrementAndGet()源码解析

/**     * Atomically increments by one the current value.     *     * @return the updated value     */    public final long incrementAndGet() {        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;    }    public final long getAndAddLong(Object var1, long var2, long var4) {        long var6;        do {            var6 = this.getLongVolatile(var1, var2);        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));        return var6;    }

我们来看看getAndAddLong方法,发现内部使用了一个 do while 循环,我们看看循环的条件是什么

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这是循环条件的源码,不知道你们发现没有一个关键字:native,表示java代码已经走完了,这里需要调用C/C++代码,这里调用的时C++代码,在这里就要解释一下为什么原子类的比较和赋值是线程安全的,那是因为c++代码中是有加锁的,不知道你们是否了解过内存与cpu的消息总线制,c++就是再消息总线中加了lock,保证了互斥性,所以对比和赋值是一个原子操作,线程安全的。

Unsafe,这个类可以为原子工具类提供硬件级别的原子性,虽然我们java中使用的这些原子工具类虽然都是无锁的,但是我们无需考虑他的多线程安全问题。

为什么原子类比互斥锁的效率低?

好了,现在来思考一下为什么原子工具类的效率会比互斥锁低?明明没有加锁,反而比加了锁慢,这是不是有点不合常理?其实这很符合常理,我们一起来分析一波,CAS(Compare and Swap)重在比较,我们看源码的时候发现有一个 do while循环,这个循环的作用是什么呢?

1.判断期望值是否和内存中的值一致;

2.如果不一致,获取内存中最新的值(var6),此时期望值就等于了var6,使用改期望值继续与内存中的值做对比,直到发现期望值和内存中的值一致,+1之后返回结果。

这里有一个问题不知道你们发现没有,就是这个循环问题,1.我们假设线程1最先访问内存中的num值=0;加载到cpu中;2.还没有做累加操作,cpu执行了线程切换操作;3.线程2得到了使用权,线程2也去内存中加载num=0,累加之后将结果返回到了内存中;4.线程切回线程1,接着上面的操作,要和内存中的num进行对比,期望值=0,内存num=1,法向对比不上,从新获取内存中的num=1加载到线程1所在的cpu中;5.此时线程又切换了,这次切换了线程3;6.线程3从内存中加载num=1到线程3所在的cpu中,之后拿着期望值=1与内存中的num=1做对比,发现值并没有被修改,此时,累加结果之后写回内存;7.线程1拿到使用权;8.线程1期望值=1与内存num=2做对比,发现又不相同,此时又需要将内存中的新num=3加载到线程1所在的cpu中,然后拿着期望值=2与内存num=2做对比,发现相同,累加将结果写回内存。

这是再多线程下的一种情况,我们发现线程1做了两次对比,而真正的程序循环对比的次数肯定会比我们分析的多,互斥锁三个线程累加10w,只需要累加30万次即可,而原子类工具需要累加30万次并且循环很多次,可能几千次,也可能几十万次,所以再内存中累加操作互斥锁会比原子类效率高,因为内存的执行效率高,会导致一个对比执行很多循环,我们称这个循环叫:自旋。

是不是所有情况下都是互斥锁要快呢?肯定不是的,如果操作的数据再磁盘中,或者操作数据量太多时,原子类就会比互斥锁的性能高很多,这很好理解,就像内存中单线程比多线程效率会更高(一般情况)。

CAS的ABA问题

ABA是什么?我们来举个例子:变量a初始值=0,被线程1获取a=0,切换到线程2,获取a=0,并且将a修改为1写回内存,切换到线程3,再内存中获取数据a=1,将数据修改为0然后写回内存,切换到线程1,这时候线程1发现内存中的值还是0,线程1认为内存中a没有被修改,这时候线程1将a的值修改为1,写回内存。

我们来分析一下这波操作会不会有风险,从表面上看,好像没什么问题,累加或者值修改的时候问题不大,觉得这个ABA没有什么风险,如果你这样认为,那就大错特错了,我举个例子,用户A用网上银行给用户B转钱,同时用户C也在给用户A转钱,我们假设用户A账户余额100元,用户A要给用户B转100元,用户C要给用户A转100元,用户A转给用户B、用户C转给用户A同时发生,但由于用户A的网络不好,用户A点了一下之后没有反应,接着又点了一下,这时候就会发送两条用户A给用户B转100元的请求。

我们假设线程1:用户A第一次转用户B100元

线程2:用户A第二次转用户B100元

线程3:用户C转用户A100元。

线程1执行的时候获取用户A的余额=100元,此时切换到了线程2,也获取到了用户A的余额=100元,线程2做了扣钱操作(update money-100 where money=100),100是我们刚查出来的,扣完之后余额应该变成了0元,切换到线程3,用户C转给用户A100元,此时用户A的账户又变成了100元,切换到线程1,执行扣钱操作(update money-100 where money=100),本来是应该扣钱失败的,由于用户C给用户A转了100元,导致用户A的余额又变成了100元,所以线程1也扣钱成功了。

这是不是很恐怖?所以在开发的时候,ABA问题是否需要注意,还请分析好应用场景,像之前说的这个ABA问题,数据库层面我们可以加版本号(版本号累加)就能解决,程序中原子类也给我们提供了解决方案:AtomicStampedReference,感兴趣的小伙伴可以研究一下。其实思路和版本号类似,比较的时候不仅需要比较期望值,还要对比版本号,都相同的情况下才会做修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/245419.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

externalreferences 命令在 sdi 模式下不可用_一个适合新手交互式Git命令学习项目

前言在我们日常工作开发中&#xff0c;Git是必不可少的版本控制软件&#xff0c;很多时候我们都用Git来管理我们的项目。比较常用的有Github&#xff0c;Gitlab&#xff0c;Stash等。因此对于Git命令的掌握是我们工作必备的能力。今天分享一个Git命令学习项目&#xff1a;learn…

sqlyong 删除数据能否撤回_数据结构知识点总结

some quoted by Fundebug&#xff1a;代码面试需要知道的8种数据结构(附面试题及答案链接)​zhuanlan.zhihu.comadded with other sources8 种常用数据结构数组栈队列链表图树哈希表priorityqueue1. 数组数组(Array)大概是最简单&#xff0c;也是最常用的数据结构了。其他数据结…

matlab 点云特征_基于点云的3D障碍物检测

击上方“新机器视觉”&#xff0c;选择加"星标"或“置顶”重磅干货&#xff0c;第一时间送达基于点云的3D障碍物检测主要有以下步骤&#xff1a;点云数据的处理基于点云的障碍物分割障碍物边框构建点云到图像平面的投影点云数据的处理KITTI数据集KITTI数据集有四个相…

重定义 不同的基类型_镍及铁镍基耐蚀合金高温合金哈氏合金镍基合金之第一篇概述...

纯镍是一种重要的应用于工业的原材料&#xff0c;它除具有良好 的强度、塑韧性外&#xff0c;在卤族元素及其氢化物活泼性气体、苛 性介质、不含氧和氧化剂的还原性酸介质中还具有良好的耐 蚀性&#xff0c;因此纯镍作为耐蚀金属材料得到广泛应用。此外&#xff0c;由 于提高耐…

cad统计面积长度插件vlx_用了它,画cad施工图再也不加班了!

文尾左下角阅读原文看视频教程好课推荐&#xff1a;1、CAD2014&#xff1a;点击查看 2、室内CAD&#xff1a;点击查看 3、CAD2019&#xff1a;点击查看4、CAD2018&#xff1a;点击查看5、Bim教程&#xff1a;点击查看6、室内手绘&#xff1a;点击查看7、CAD三维&#xff1a;点击…

mysql 视图 查询速度慢_mysql 视图查询速度慢

场景&#xff1a;表 stockpooldata_flashCREATE TABLE stockpooldata_flash (id bigint(15) NOT NULL AUTO_INCREMENT,formula_id int(8) DEFAULT NULL,period_type tinyint(3) DEFAULT NULL,gpMarket int(4) DEFAULT NULL,gpcode varchar(20) DEFAULT NULL,ymd int(11) DEFAUL…

局部页面切换url为什么不变_python爬虫 - 翻页url不变网页的爬虫探究

python爬虫-翻页url不变网页的爬虫探究url随着翻页改变的爬虫已经有非常多教程啦&#xff0c;这里主要记录一下我对翻页url不变网页的探究过程。学术菜鸡第一次写CSDN&#xff0c;请大家多多包容&#xff5e; 如果对你有一点点帮助&#xff0c;请帮我点个赞吧&#xff01;翻页u…

python波峰波谷算法_波动均分算法

波动均分算法by leeenx on 2018-01-11「波动」和「均分」大部分读者朋友是知道的&#xff0c;但看到「波动均分」应该是一头雾水的。其实&#xff0c;这个名词是笔者拼凑出来的。什么是「波动均分」&#xff1f;把指定的数值 A&#xff0c;分成 N 份&#xff0c;此时每份的数值…

java web 注册登录_javaweb实现登录注册功能实例

前期呢&#xff0c;我们学习了javaweb项目用JDBC连接数据库&#xff0c;还有数据库的建表功能&#xff0c;今天&#xff0c;我们来看一下javaweb实现登录注册功能实例&#xff0c;javaweb项目使用的工具是eclipse&#xff0c;最后把项目部署在了Tomcat中&#xff0c;连接数据库…

source insight 函数不能跳到definition_小技能: Windows10突然不能复制粘贴谁搞鬼

最近连续遇到几次&#xff0c;电脑突然不能复制粘贴了&#xff0c;非常影响工作。(如果不想听我扯&#xff0c;就直接跳到最后看结果啊&#xff0c;我真贴心。)你们都懂得&#xff0c;程序员嘛&#xff0c;用的最多的就是ctrlc&#xff0c;ctrlv。这不能用了&#xff0c;不是让…

hsv 明度的范围_通过HSV转换的方式实现图片数据增强

在我的上一篇文章中&#xff0c;我记录了自己将MOT17-Det数据集转换成VOC格式&#xff1a;HUST小菜鸡&#xff1a;将MOT17-Det数据集转成VOC格式​zhuanlan.zhihu.com但是在后期的测试过程中&#xff0c;发现了一些小问题&#xff1a;首先是train.txt里面写入的图片数和标注的数…

java protected关键字_Java 权限protected关键字纠正

以前一直认为自己理解了Java四种权限访问&#xff0c;昨天突然编程时发现protected居然在子类中不能调用&#xff0c;然后越看越迷糊&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;public&#xff1a; Java语言中访问限制最宽的修饰符&#xff0c;…

互联网java常用框架_来,带你鸟瞰 Java 中4款常用的并发框架!

1. 为什么要写这篇文章几年前 NoSQL 开始流行的时候&#xff0c;像其他团队一样&#xff0c;我们的团队也热衷于令人兴奋的新东西&#xff0c;并且计划替换一个应用程序的数据库。 但是&#xff0c;当深入实现细节时&#xff0c;我们想起了一位智者曾经说过的话&#xff1a;“细…

2020亚太杯数学建模_比赛 | 2020年APMCM亚太地区大学生数学建模竞赛

2020年11月26日到30日&#xff0c;在我院老师指导下&#xff0c;由统计分析竞赛社组织的41支队伍&#xff0c;共123人&#xff0c;参加了亚太地区大学生数学建模竞赛组委会主办的大学生学科类竞赛。此次竞赛题目分为A题和B题&#xff0c;参赛者需从A&#xff0c;B两题中任选其一…

java声明复数类_JAVA声明复数类

声明复数类&#xff0c;成员变量包括实部和虚部&#xff0c;成员方法包括实现由字符串构造复数、复数加法、减法&#xff0c;字符串描述、比较相等等操作。虽然我只是一个刚学一个月JAVA的菜鸡&#xff0c;但是强迫症让我把复数乘法和除法一起写出来了。public class Complex {…

sql 没有调试 菜单_MySQL递归查询上下级菜单

正文在传统的后台管理系统里面经常会需要展示多级菜单关系&#xff0c;今天我们来学一下如何使用一条SQL语句展示多级菜单。现在我们有一张corpinfo单位表&#xff0c;里面有一个belong字段指向上级单位&#xff0c;首先来看一下现在表里有什么数据&#xff1a;SELECT uid,ubel…

java 桥 word_java导出word的6种方式(转发)

最近做的项目&#xff0c;需要将一些信息导出到word中。在网上找了好多解决方案&#xff0c;现在将这几天的总结分享一下。目前来看&#xff0c;java导出word大致有6种解决方案&#xff1a;1&#xff1a;Jacob是Java-COM Bridge的缩写&#xff0c;它在Java与微软的COM组件之间构…

jieba 词典 词频_在Hanlp词典和jieba词典中手动添加未登录词

在使用Hanlp词典或者jieba词典进行分词的时候&#xff0c;会出现分词不准的情况&#xff0c;原因是内置词典中并没有收录当前这个词&#xff0c;也就是我们所说的未登录词&#xff0c;只要把这个词加入到内置词典中就可以解决类似问题&#xff0c;如何操作呢&#xff0c;下面我…

python爬取汽车之家_python爬取 汽车之家(汽车授权经销商)

一&#xff1a;爬虫的目标&#xff1a;打开汽车之家的链接&#xff1a;https://www.autohome.com.cn/beijing/&#xff0c;出现如下页面我们的目标是点击找车&#xff0c;然后出现如下图我们要把图中的信息抓取到二&#xff1a;实现过程我们选择 宝马5系 然后点击找车注意宝马…

Java 调用 Caffe_解决 free(): invalid pointer: 0x00000000019ff700 运行时报错(caffe)(libtool使用)...

编译成功&#xff0c;运行时报错&#xff1a;在使用 pytorch or tensorflow or caffe 时&#xff0c;都可能存在这个问题&#xff1a;*** Error in xxx: free(): invalid pointer: 0x00000000020663b0 ***很可能是缺少libtcmalloc库解决方法1&#xff1a;apt-get安装libtcmallo…