认识线程安全前需要先引入与线程安全密不可分的一个概念:共享资源。
所谓的共享资源,就是一个资源被多个线程所共同持有或访问。
在早期我们刚接触编程的时候,首先学习的就是相应的编程语法,和基础的程序设计(算法),刷刷oj。这时我们所编写的代码程序还都处于单线程的顺序执行时期,这时我们所写的程序肯定是线程安全的,过渡到多线程环境下也一样,线程安全就是指在写这些程序时我们不需要去额外的考虑线程的调度和交替执行,也不用去做额外的同步,我们就可以获得正确的结果。
相对的线程安全问题就是指,在多线程环境下,读写一个共享资源,由于没有任何的同步措施,导致结果错误或者脏数据等不可遇见的问题。
Java内存模型允许编译器和处理器对指令重排序以提高运行性能,但是只会对不存在数据依赖性的指令重排。
由于这一特性,所以在单线程情况下并不会影响最终的结果。但是在多线程情况下由于指令的重排可能会出现与预期结果不一致等问题:
public class ResetProblem extends Thread {
private static int num = 0;
private static boolean flag = false;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println(num+" "+flag); //①
}
}
static class WThread extends Thread{
@Override
public void run() {
num = 1; //②
flag = true; //③
}
}
public static void main(String[] args) throws InterruptedException {
ResetProblem resetProblem = new ResetProblem();
resetProblem.start();
WThread writethread = new WThread();
writethread.start();
Thread.sleep(1000);
resetProblem.interrupt();
}
}
如上述代码,我们理想化的执行顺序是②->③->①,但是由于因为操作①②①并不存在相互依赖关系,为了优化会发生1指令重排,所以在多线程的环境下可能实际的执行顺序为②->①->③,导致结果并不是预计的(1,true),而是(0,true);
Java内存模型规定了所有的变量都存储在主存中,当线程使用变量时,会将内存中的变量复制到自己的工作内存中,线程对变量的所有操作(读写等)都必须在工作内存中进行,而不 能直接对主存进行操作。
基于这种规定,那么我们现在假设有两个线程A,B共享一个变量X,X初始值为0,会出现什么情况呢?
线程B获取到的变量值,并不是已被线程A修改后的变量值,也就是说线程B写入的值对线程A不可见,这就是共享变量的内存不可见问题。
在原子刚被发现的时候被认为是不可分割的,尽管已经发现了比原子更小的中子、质子和夸克。所以我们对原子性操作的定义就是,执行的一系列操作要么全部执行,要么全部不执行,是不可分割的操作。 J a v a 虚 拟 机 规 定 lock、unlock、read、load、use、assign、store、write这八种操作是原子性的操作。
举两个简单的例子:
int i = 1;
i++
就不是一个原子操作,因为这个看上去只要一段代码就能实现的计数器操作,需要读-改-写三个步骤,而且在每个步骤之间都有被打断的可能;如下图所示看一下,在多线程情况下i++操作可能发生的安全问题:
所以说在这种情况下,就算线程A和线程B的共享内存可见,由于该操作不是原子操作,也不能保证线程的安全性。
public static int i = 0;
public static void main(String[] args) throws InterruptedException {
Runnable r = ()->{
for (int j = 0; j < 100000; j++) {
i++;
}
};
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);
}
我们的预期值是200000,但是实际确是140901,而且每次执行结果都不一样。
if(map.containsKey(key)){
map.remove(key);
}
线程A先进入if语句中,之后切换线程为线程B,线程B进入if语句中并执行的remove操作,然后切换为线程A,但是实际上线程B已经进行该操作,所以此处会导致线程不安全问题。
下篇我们来聊一聊,如何解决线程的安全问题,和synchronized、volatile关键字
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有