Java中的线程安全问题总结

一、线程安全问题
1.产生原因
  我们使用java多线程的时候,最让我们头疼的莫过于多线程引起的线程安全问题,那么线程安全问题到底是如何产生的呢?究其本质,是因为多条线程操作同一数据的过程中,破坏了数据的原子性。所谓原子性,就是不可再分性。为什么说破坏了数据的原子性就会产生的线程安全问题呢?我们用一个非常简单的例子来说明这个问题。

int i = 1;
int temp; 
while(i < 10){
temp = i; //读取i的值
i = temp + 1; //对i进行+1操作后再重新赋给i
};

上面的的代码其实就是读取i当前的值、对读取到的值加1然后再赋给i
  我们知道,在某一个时间点,系统中只会有一条线程去执行任务,下一时间点有可能又会切换为其他线程去执行任务,我们无法预测某一时刻究竟是哪条线程被执行,这是由CPU来统一调度的。因此现在假设我们有t1、t2两条线程同时去执行这段代码。假设t1执行完第5行代码停住了(需要等待CPU下次调度才能继续向下执行),此时t1读到i的值是1。然后CPU让t2执行,注意刚才t1只执行完了第5行,也就是说t1并没有对i进行加1操作然后再赋回给i,因此这是i的值还是1,t2拿到i=1后一路向下执行直到结束,当执行到第6行的时候对i进行加1并赋回给i,完成后i的值变为2。好了,此时CPU又调度t1让其继续执行,重点在这里,还记不记得t1暂停前读取到的i是几?没错是1,此时t1执行第6行代码,对i进行加1得到的结果是2然后赋回给i。好了,问题出来了,我们清楚的直到循环进行了两次,按正常逻辑来说,对i进行两次加1操作后,此时i应该等于3,但是两条线程完成两次加1操作后i的值竟然是2,当进行第三次循环的时候,读取到i的值将会是2,这样的结果是不是很诡异,这就是线程安全问题的产生。那么引发这个问题的原因是什么呢?其实就是将读和写进行了分割,当读和写分割开后,如果一条线程读完但未写时被CPU停掉,此时其他线程就有可能趁虚而入导致最后产生奇怪的数据。
  下面画图说明:
 在这里插入图片描述
 我们可以把左边的圆看成是符合原子性(即一步执行)的代码,而右边的圆是被分割成了两步执行的代码。如果数据没有破坏原子性,由于线程被调度一次的最少要执行1行代码,那么t1只要执行了这行代码,就会连读带写全部完成,其他线程再拿到的数据就是被写过的最新数据,不会有任何安全隐患;而如果数据破坏了原子性,将读写进行了分割,那么t1,读取完数据如果停掉的话,t2执行的时候拿到的就是一个老数据(没有被更新的数据),接下来t1,t2同时对相同的老数据进行更新势必会因此数据的异常。
 二、使用synchronized解决线程安全问题
1.synchronized的概念
  synchronized在英语中翻译成同步,同步想必大家都不陌生。例如同步调用,有A,B两个方法,必须要先调用A并且获得A的返回值才能去调用B,也就是说,想做下一步,必须要拿到上一步的返回值。同样的道理,使用了synchronized的代码,当线程t1进入的时候,另一个线程若t2想进入,就必须要得到返回值才能进入,怎么得到返回值呢?那就要等t1出来了才会有返回值。这就是多线程中常说的加锁,使用synchronized的代码我们可以想象成将他们放到了一个房间,返回值就相当于这个房间的钥匙,进入这个房间的线程同时会把钥匙带进去,当它出来的时候会将钥匙仍在地上(释放资源),然后其他线程过来抢钥匙(争夺CPU执行权),以此类推。
  被放到房间里代码,其实就是为了让其保持原子性,因为当线程t1进入被synchronized修饰的代码当中的时候,其他线程是被锁在外边进不来的,知道线程t1执行完里边的所有代码(或抛出异常),才会释放资源。我们换个角度想,这不就是让房间(synchronized)里面的代码保持了原子性吗,某一线程只要进去了,就必须要执行完毕里边的代码别的线程再进去,期间不会有其他线程趁虚而入来干扰它,就像我上面图中左边那个圆一样,也就是相当于将本来分割的读和写的操作合并在了一起,让一个线程要么不执行,只要执行就得把读和写全部执行完(且期间不会受干扰)。
2.synchronized的三种用法
(1)同步代码块
(2)同步函数
(3)静态同步函数

注意
  对于线程安全问题,需要注意以下:

  1. 只存在读数据的时候,不会产生线程安全问题。
  2. 在java中,只有同时操作成员(全局)变量的时候才会产生线程安全问题,局部变量不会。
  3. 多线程中是共享内存的。
  4. 线程都有自己的工作内存,在取一个值时,会向共享内存copy到自己的工作内存;于是乎,当你多个线程同时存在,就有问题了,你两个线程同时是操作共享内存的一个数据,如果不加锁,两个线程对这个数据的操作和判断,你不可控;因为CPU执行两个线程的代码可能每次顺序都不一样的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值