线程安全:

线程不安全现象出现的原因

开发者角度

1.多个线程之间操作同一块数据了(共享数据)——不仅仅是内存数据
2.至少有一个线程在修改这块共享数据,多个线程中至少有一个对共享数据做修改操作

系统角度

前置知识:
1.Java代码中的一条语句,很可能对应多条指令,r++实际上就是r=r+1
变成指令动作:
1.从内存中(r代表的内存区域)把数据加载到寄存器中
2.完成数据加一的操作
3.把寄存器中的值写回到内存中(r代表的内存区域)

2.线程调度是可能发生在任意时刻的,但是不会切割指令(一条指令只有执行完或者完全没有执行这两种状态)
在这里插入图片描述

原子性

程序员的预期是r++或者r–是一个原子性的操作(全部完成或者全部没完成),但实际执行起来,保证不了原子性,所以会出错。
在这里插入图片描述

原子性被破坏是线程不安全的最常见的原因

为什么COUNT越大,出错概率越大

COUNT越大,线程执行需要跨时间片的概率越大,导致中间出错的概率越大,碰到线程调度的概率也就越大

系统角度分析出现线程不安全的原因——内存可见性问题

前置知识:CPU中为了提升数据获取速度,一般在CPU中设置缓存 ,指令的执行速度》》内存的读写速度
在这里插入图片描述
在这里插入图片描述

主内存和工作内存

JVM规定了JVM内存模型,把一个线程想成一个CPU,主存储和主内存就是真实内存,工作存储或者工作内存就是CPU中缓存的模拟
在这里插入图片描述
1.把r从主内存放到当前线程的工作内存中
2.循环r++,完成1000次(在工作内存中完成),中间允许同步回主内存
3.在1000放回r

线程的所有数据操作必须:

1.从主内存加载到工作内存中
2.在工作内存中进行处理,允许在工作内存中处理很久
3.完成最终的处理之后,再把数据同步回主内存

内存可见性:

一个数据对线程的操作,很可能其他线程无法感知,甚至在某些情况下会被优化成完全看不到的结果

系统角度看线程不安全问题——代码重排序导致的问题

在这里插入图片描述

1.我们写程序,往往是经过中间很多环节优化的结果,并不保证最终执行的语言和我们写的语句是一模一样的,所谓的重排序就是指执行的指令和书写的指令不一致
2.JVM规定了一些重排序的基本原则:happend-before规则:
JVM要求,无论怎么优化,对于单线程的视角,结果不应该有改变,但并没有规定多线程环境的情况,导致在多线程环境下可能出问题

线程安全总结

1.什么是线程安全?

1)程序的线程安全——运行结果100%符合预期(无法实操,用来理解)
2)Java语境下,经常说某个类某个对象是线程安全的:这个类、对象的代码中已经考虑了处理多线程的问题了,如果只是简单使用,可以不考虑线程安全的问题;ArrayList就不是线程安全的——ArrayList实现中,完全没考虑过线程安全的任何问题,无法直接使用在多线程环境(无法实现多个线程同时操作同一个ArrayList)

2.作为程序员如何考虑线程安全问题?

1.尽可能让几个线程之间不做数据共享,各干各的,就不需要考虑线程安全问题了;如归并排序中,四个线程虽然处理的是同一个数组,但提前画好范围,各做各的
2.如果非要有共享操作,尽可能不去修改,而是只读操作;static final int COUNT=,即使多个线程同时使用这个COUNT也无所谓的
3.一定会出现线程问题了,问题的原因从系统角度讲:
1)原子性被破坏了
2)由于内存可见性问题,导致某些线程读到脏
3)由于代码重排序导致的线程之间关于数据的配合出问题了

一些常见类的线程安全问题

ArrayList、LinkedList、PriorityQueue、TreeMap、TreeSet、HashMap、HashSet、StringBuilder都不是线程安全的

ArrayList为什么不是线程安全的

多个线程同时对一个ArrayList对象有修改操作时,结果会出错
在这里插入图片描述

最常见的违反原子性的场景

1.read-write场景
i++;
array[size]=e;
size++;
2.check-update场景
if(a==10){
a=…;
}

锁(lock)

synchronize

语法:

1.修饰方法(普通、静态方法)——》同步方法
synchronize int add(…){

}
2.同步代码块
synchronize(引用){

}

类名.class
是一个引用,指向关于这个类对象(不是这个类实例化出来的对象而是这个类数据表现出的对象,每个被加载进来的类都可以通过类名。class访问到)

猜你喜欢

转载自blog.csdn.net/weixin_45715131/article/details/124455686
今日推荐