线程安全

1 什么是线程安全?“线程安全”也不是指线程的安全,而是指内存的安全。为什么如此说呢?这和操作系统有关。目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。假设某个线程把数据处理到一半,觉得很累,就去休息了一会,回来准备接着处理,却发现数据已经被修改了,不是自己离开时的样子了。可能被其它线程修改了。
比如把你住的小区看作一个进程,小区里的道路/绿化等就属于公共区域。你拿1万块钱往地上一扔,就回家睡觉去了。睡醒后你打算去把它捡回来,发现钱已经不见了。可能被别人拿走了。
因为公共区域人来人往,你放的东西在没有看管措施时,一定是不安全的。内存中的情况亦然如此。
所以线程安全指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。
即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。

解决问题的过程其实就是一个取舍的过程,不同的解决方案有不同的侧重点。现实中很多人都会把1万块钱藏着掖着,不让无关的人知道,所以根本不可能扔到大马路上。因为这钱是你的私有物品。

在程序中也是这样的,所以操作系统会为每个线程分配属于它自己的内存空间,通常称为栈内存,其它线程无权访问。这也是由操作系统保障的。如果一些数据只有某个线程会使用,其它线程不能操作也不需要操作,这些数据就可以放入线程的栈内存中。较为常见的就是局部变量。

也就是说这些局部变量会在每个线程的栈内存中都分配一份。由于线程的栈内存只能自己访问,所以栈内存中的变量只属于自己,其它线程根本就不知道。-----------------------就像每个人的家只属于自己,其他人不能进来。所以你把1万块钱放到家里,其他人是不会知道的。且一般还会放到某个房间里,而不是仍在客厅的桌子上。所以把自己的东西放到自己的私人地盘,是安全的,因为其他人无法知道。而且越隐私的地方越好。-------------相信聪明的你已经发现,上面的解决方案是基于“位置”的。因为你放东西的“位置”只有你自己知道(或能到达),所以东西是安全的,因此这份安全是由“位置”来保障的。在程序里就对应于方法的局部变量。局部变量之所以是安全的,就是因为定义它的“位置”是在方法里。这样一来安全是达到了,但是它的使用范围也就被限制在这个方法里了,其它方法想用也不用了啦。

1 ThreadLocal

回到程序里,要让公共区域堆内存中的数据对于每个线程都是安全的,那就每个线程都拷贝它一份,每个线程只处理自己的这一份拷贝而不去影响别的线程的,这不就安全了嘛。相信你已经猜到了,我要表达的就是ThreadLocal类了。每个线程在运行时都会拷贝一份存储到自己的本地。线程类(Thread)有一个成员变量,类似于Map类型的,专门用于存储ThreadLocal类型的数据。从逻辑从属关系来讲,这些ThreadLocal数据是属于Thread类的成员变量级别的。从所在“位置”的角度来讲,这些ThreadLocal数据是分配在公共区域的堆内存中的。说的直白一些,就是把堆内存中的一个数据复制N份,每个线程认领1份,同时规定好,每个线程只能玩自己的那份,不准影响别人的。需要说明的是这N份数据都还是存储在公共区域堆内存里的,经常听到的“线程本地”,是从逻辑从属关系上来讲的,这些数据和线程一一对应,仿佛成了线程自己“领地”的东西了。其实从数据所在“位置”的角度来讲,它们都位于公共的堆内存中,只不过被线程认领了而已。这一点我要特地强调一下。其实就像大街上的共享单车。原来只有1辆,大家抢着骑,老出问题。现在从这1辆复制出N辆,每人1辆,各骑各的,问题得解。共享单车就是数据,你就是线程。骑行期间,这辆单车从逻辑上来讲是属于你的,从所在位置上来讲还是在大街上这个公共区域的,因为你发现每个小区大门口都贴着“共享单车,禁止入门”。哈哈哈哈。共享单车是不是和ThreadLocal很像呀。再重申一遍,ThreadLocal就是,把一个数据复制N份,每个线程认领一份,各玩各的,互不影响。只能看,不能摸

只读变量,常量

放在公共区域的东西,只是存在潜在的安全风险,并不是说一定就不安全。有些东西虽然也在公共区域放着,但也是十分安全的。比如你在大街上放一个上百吨的石头雕像,就非常安全,因为大家都弄不动它。
再比如你去旅游时,经常发现一些珍贵的东西,会被用铁栅栏围起来,上面挂一个牌子,写着“只能看,不能摸”。当然可以国际化一点,“only look,don’t touch”。这也是很安全的,因为光看几眼是不可能看坏的。
回到程序里,这种情况就属于,只能读取,不能修改。其实就是常量或只读变量,它们对于多线程是安全的,想改也改不了。比如把及格分数设定为60分,在前面加上一个final,这样所有线程都动不了它了。这就很安全了。

2 使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:
final StringBuffer a=new StringBuffer(“immutable”);
执行如下语句将报告编译期错误:
a=new StringBuffer("");
但是,执行如下语句则可以通过编译:
a.append(" broken!");
有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进来的参数对象:
publicvoid method(final StringBuffer param){
}
实际上,这是办不到的,在该方法内部仍然可以增加如下代码来修改参数对象: param.append(“a”);

public static void method2(final StringBuffer buffer) {
buffer.append(“buffer”);// 编译通过,因为final修饰的是引用类型的变量
}

总结:引用不可变,引用指向的内容是可变的;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值