线程问题
一、什么是线程安全问题?
1.什么是线程安全问题?刚听到这个词大脑一篇空白,内心n个问号。按照惯例我查了百度,百度百科是这样解释的
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
为了能直观展示多线程安全问题,我模拟了多窗口卖票场景(假如某演唱会剩余5张票,有A、B、C、D四个窗口同时售票场景)
模拟卖票窗口类:
package spittr.test;
public class TicketRunnable implements Runnable{
private int num = 5;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + " *********************正在出售第" + num + "张票");
try {
Thread.sleep(10); //提高安全问题的概率,让线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 正在出售第" + num + "张票");
num = num - 1;
}
}
}
}
四个窗口同时售票时:
public static void main(String[] args) {
TicketRunnable runnable = new TicketRunnable();
Thread A = new Thread(runnable);
A.setName("A窗口");
Thread B = new Thread(runnable);
B.setName("B窗口");
Thread C = new Thread(runnable);
C.setName("C窗口");
Thread D = new Thread(runnable);
D.setName("D窗口");
A.start();
B.start();
C.start();
D.start();
}
模拟结果如下表格:
场景 | 控制台输出 |
期望结果 | A窗口 *********************正在出售第5张票 A窗口正在出售第5张票 B窗口 *********************正在出售第4张票 B窗口正在出售第4张票 C窗口 *********************正在出售第3张票 C窗口 正在出售第3张票 B窗口 *********************正在出售第2张票 B窗口 正在出售第2张票 D窗口 *********************正在出售第1张票 D窗口 正在出售第1张票 |
实际结果 | A窗口 *********************正在出售第5张票 C窗口 *********************正在出售第5张票 D窗口 *********************正在出售第5张票 B窗口 *********************正在出售第5张票 A窗口 正在出售第5张票 A窗口 *********************正在出售第4张票 C窗口 正在出售第4张票 C窗口 *********************正在出售第3张票 B窗口 正在出售第4张票 B窗口 *********************正在出售第2张票 D窗口 正在出售第4张票 D窗口 *********************正在出售第1张票 A窗口 正在出售第1张票 B窗口 正在出售第0张票 D窗口 正在出售第0张票 C窗口 正在出售第0张票 |
提问:控制台输出数据,出现了重票、不存在的票,这是为什么呢?
某些JVM运行中,有两块主要的内存,一个是主内存,另外一个是每个线程都具有的工作内存。
线程内存模型如下:
线程运行的流程为:
从主内存中copy要操作的数据到自己的工作内存中去。 2. 线程主体从自己的工作内存中读取数据进行操作。 3. 操作完成后,再同步到主内存中去。
由此可以推出控制台打印出现重复的数据现象是由于:
A、B、C、D其中一个线程(假如是B线程)修改工作内存
中的变量把num = 4,修改为3
,还没来得及把num = 3
同步到虚拟机主内存
中,其他线程读取了未修改数据num= 4
,也进行num - 1 操作,得到num = 3, 并把num = 3 同步到主内存中,这时B线程又得到CPU资源继续执行没有完成的同步操作,又打印出num = 4.
A窗口 *********************正在出售第4张票
C窗口 正在出售第4张票
C窗口 *********************正在出售第3张票
B窗口 正在出售第4张票
B窗口 *********************正在出售第2张票
D窗口 正在出售第4张票
D窗口 *********************正在出售第1张票
A窗口 正在出售第1张票
B窗口 正在出售第0张票
D窗口 正在出售第0张票
C窗口 正在出售第0张票
出现不存在的数据现象的原因:
线程B、D、C走到 if 条件num > 0 代码时,B、D、C获取的num值分别为:2、1、3 , A线程 进行num - 1 操作后并及时把num = 1 值同步到主内存
中,B、D、C线程获取CPU执行权后,获取num = 1 到线程工作内存中,进行num - 1 操作,又分别 把num = 0 同步到 JVM主内存中。
由此可见,当多个线程共享资源时,就会出现安全问题。那我们如何解决线程安全问题呢?未完待续…
线程安全包含原子性和可见性两个方面, Java的同步就在都是围绕这两个方面来确保线程安全的
什么是可见性?可见性就是volatile修饰变量表现出来的性质,使变量在多个线程中可见
什么是原子性操作?原子性操作就是不可分的操作,int i = a;就是原子性操作,而上述count++就不是原子性操作
上图是对线程内存模型进一步的描述,一线程在执行use操作时,突然时去了cpu执行权限(cpu执行任何原子性操作时,是不可能出现中断的),也就出现上述非线程安全的问题了。