不理解的人一般会说,线程安全就是指,线程的安全啊,这回答了等于没说!
线程安全其实指的是内存的安全,为什么这么说呢?这还是和操作系统有关系!
操作系统一般都是多任务的,就是多个进程同时进行(准确来说是cpu切换的速度太快了),所以每个进程之间访问的数据要保证安全,所以每个进程都只能访问自己的内存,不能访问别的进程,这个操作系统已经处理过了.
但是每个进程除了自己私有的一个内存空间外,还有一个公共的区域,"堆"内存;进程中的所有线程都可以访问该区域,这也就会造成一定的安全隐患.
这就相当于你把你最新款的水果手机放到了二七广场上,一天后,你又过去拿手机,发现没了~不要感到意外和惊讶,因为你把手机(数据)放到了广场("堆"内存),而路人就相当于每个进程,去访问了手机(数据),所以你发现手机没了,你甚至都不知道谁拿走了~
那你说这个问题可以解决吗?
答案是肯定的!你手机拿在手里不就好了,干嘛放在广场呢!
实际上,没有人会把手机放到广场上的,因为手机是你的私人物品.
程序中也是如此,操作系统会为每个程序分配一个空间,叫做"栈"内存,其他线程无权访问,这个是操作系统处理过的.
如果数据被放到栈内存中,就可以保证其他线程不能访问,进而达到线程安全问题,程序中常见的作法就是:局部变量
public int sum(int[] numbers){
int count=0;
for (int num : numbers) {
count += num;
}
return count;
}
上面实例中的count 就属于局部变量.它就会被分配到栈内存空间中.
每个线程访问进来,都为为每个线程分配自己的内存空间,所以就能保证线程安全;
手机放到公共区域,个人肯定不会这么干!
但是,有一个地方会,就是线下体验店,比如小米线下体验店吧,里面放的有所有的小米最新出的产品,你可以进去随便玩,但是就是不能带走,因为每个产品都有一个警报先,你敢拿下来,就响......(付钱的可以拿走哦)
在程序中,就是加一个final最终的,就是线程可以使用,但是不能修改!
public static final String DATE_FORMAT = "yyyy-MM-dd";
一种情景就是,你去超时购物,把电动车放到超时门口,然后你会怎么做呢?
对,就是加一把锁,然后你购物回来,你的车就还在那里.超市门口属于你的私有空间吗?肯定不属于了,但为什么可以存放你的私人物品呢?原因很简单,就是锁.
程序也是一样.如果公共区域(堆内存)的数据,要被多个线程操作时,为了确保数据的安全(或一致)性,需要在数据旁边放一把锁,要想操作数据,先获取锁再说吧。
假设一个线程来到数据跟前一看,发现锁是空闲的,没有人持有。于是它就拿到了这把锁,然后开始操作数据,干了一会活,累了,就去休息了。
这时,又来了一个线程,发现锁被别人持有着,按照规定,它不能操作数据,因为它无法得到这把锁。当然,它可以选择等待,或放弃,转而去干别的。
第一个线程之所以敢大胆的去睡觉,就是因为它手里拿着锁呢,其它线程是不可能操作数据的。当它回来后继续把数据操作完,就可以把锁给释放了。锁再次回到空闲状态,其它线程就可以来抢这把锁了。还是谁先抢到锁谁操作数据。
class ClassAssistant {
double totalScore = 100;
final Lock lock = new Lock();
void addScore(double score) {
lock.obtain();
totalScore += score;
lock.release();
}
void subScore(double score) {
lock.obtain();
totalScore -= score;
lock.release();
}
}
假定一个班级的初始分数是100分,这个班级抽出10名学生来同时参加10个不同的答题节目,每个学生答对一次为班级加上5分,1答错一次减去5分。因为10个学生一起进行,所以这一定是一个并发情形。
因此加分和减分这两个方法被并发的调用,它们共同操作总分数。为了保证数据的一致性,需要在每次操作前先获取锁,操作完成后再释放锁。
生活场景:比如公司发放中秋礼品,每个人发一个666g重的纯金月饼.比如公司100个人,如果老板说,大家自己抢吧,抢到多少都是你的,那么肯定会发生一件事,有的人抢了多个,有的人没抢到.
但是如果礼品是每个人都去财务室领取,然后领取完毕签字呢?肯定就不会出问题了吧.
在程序上就是:
要让公共区域堆内存中的数据对于每个线程都是安全的,那就每个线程都拷贝它一份,每个线程只处理自己的这一份拷贝而不去影响别的线程的,这不就安全了嘛。没错,就是用ThreadLocal类。
class Student {
ThreadLocal total = new ThreadLocal<>();
String jieguo() {
double score = total.get();
if (score >= 60) {
return "通过";
}
return "不通过";
}
}
比如张三90分,运行就是通过:李四59分,运行就是不通过;肯定不会出现张三90分,结果是不通过;
我写了这么多,当然也是有私心的,只求一个关注而已.
关注后回复(0728可以领取大厂面试题)