线程安全

线程安全

线程安全其实是内存安全。因为在每一个进程中都有一个公共区域叫做,每个线程共用堆区,如果不对堆区的数据进行管理控制,势必会造成随意修改的风险。
解决这种问题的方法有如下几种:
1、自己的东西自己好好保存
操作系统会为每个线程分配属于自己的独享空间,其他线程无权访问,这个内存通常称为栈内存。这是由操作系统保证的。如果有些数据只有某个线程使用,而其他线程不需要操作,则将这些数据保存在栈中,较为常见的是局部变量。
假如某一段代码里面有几个局部变量,A线程执行这段代码,则会在A线程的栈中分配内存空间,B线程执行这段代码也会在B线程栈中分配内存空间来保存这些局部变量。A、B线程独立的操作自己栈中的局部变量,相互不会影响。这份安全是由位置来保障的。局部变量虽然安全,但是它的使用范围受到限制,它只能在自己的方法中使用,其他方法根本用不了。
2、人人有份
如果一个变量需要在多个方法中使用,那么就需将(方法的)局部变量变成(类的)成员变量,这时候“位置”发生了变化,由一个私有内存来到共有内存,则它的潜在的安全风险也随之而来。
要想让公共区域堆内存中的数据对于每一个线程来说都是安全的,那就让每一个线程都拷贝一份,每个线程都只是处理自己拷贝的那一份,这样并不会影响到其他的线程。这里就是ThreadLocal类。
ThreadLocal <类型> thread=new ThreadLocal<>();
线程类(Thread)有一个成员变量,类似于Map类型的,专门用来存储ThreadLocal类型的数据。这些ThreadLocal相当于是Thread类成员变量级别的。这些ThreadLocal数据是分配在公共区域的堆内存中。简单来说,就是把堆内存中的一个数据复制N份,每一个线程认领一份,而且规定每个线程只能操作自己认领的那个数据。这些数据本身还是在堆内存中,只是逻辑上属于某一个线程。
3、只能看,不能摸
这里意思是只能读取不能修改,如,常量或只读变量。被final 修饰的变量。
以上三种解决方法,其实都在“避重就轻”,而实际情况纷繁复杂。
4、互斥锁
如果公共区域的数据,要被多个线程操作的时候,为了确保数据的安全性,需要在数据的旁边放一把锁,要想操作数据,先获取到锁再说。
Synchronized和Lock体系。
5、乐观锁
如果将数据放在公共各区域,但自始至终只有一个线程使用这个数据,也就是单线程模型,那么这个数据也是很安全的。这里要提到的是CAS(Compare and Swap)比较交换,假设所有线程在访问共享资源时候不会产生冲突,由于不会出现冲突也就不会有线程阻塞停顿的现象。出现冲突时无锁操作使用CAS来鉴别线程是否有冲突,出现冲突就重试当前操作直到没有冲突为止。
CAS可以理解为CAS(V,O,N)
V:是当前内存地址实际存放的值
O:期望值
N:更新的值
1)当V=O时,期望值与内存地址中的实际的值相等,说明当前内存中的值没有被修改过,然后将N值赋值给V。
2)当V!=O时,表示内存中的值已经被其他线程修改过,O值已经不是最新的值,返回V,无法进行修改。
3)当多个线程使用CAS操作时,只有一个线程会成功,其余线程均失败,失败的线程会不停地尝试(自旋)或挂起线程(阻塞)。
4)CAS会出现的问题是ABA问题
线程1在执行时,将内存中的值替换成A,然后执行其他操作,这是线程2将内存中的值替换成了B,之后又替换成了A,此时线程1返回看到内存的值还是A,认为没有线程修改内存中的数据。
解决方法:
添加版本号,1A->2B->3A
与线程阻塞相比,自旋会浪费大量的CPU资源,因为当前线程仍处于线程运行状态,只不过跑的是无用的指令。
这里解决的方法是采用自适应自旋,根据以往自旋时间来动态调整自旋时间。如果之前自旋获得了锁,则这次自旋时间稍微增长,如果之前没有获得锁,则这次自适应时间稍微缩短。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值