目录
案例
让我们来做个小测试,两个线程,每个线程循环1w次让num累加,共计2w次,执行完让我们看结果。
我们发现每次执行后num的数值都不一样,且不是我们想要的数值 - 20000
class Count {
public int num = 0;
public void increase() {
num++;
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1_0000; i++) {
count.increase();
}
}) ;
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1_0000; i++) {
count.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.num);
}
}
原因
引起线程不安全主要分为五点:
1.抢占式执行(线程不安全的万恶之源 罪魁祸首)
【多个线程的调度执行,可以视为“全随机”的】
2.多个线程修改同一个变量
3.修改操作不是原子的 【原子:表示不可分割的最小单位】
4.内存可见性问题:JVM对代码的优化
【程序员写代码的水平有高有低,因此设计编译器的大佬们为了缩小其中的差距,会对代码进行优化,将代码的执行逻辑等价转换成另一种逻辑,使代码在逻辑不变的基础上,效率获得提升】
5.指令的重排序:也是由于JVM对代码的优化所引起的问题
解决方法
原因1是内核实现的,我们无法进行干预
原因2我们可以对代码进行调整以此来规避线程不安全,但适用性较低,相对比较麻烦
因此我们将目光聚焦到第三点:
我们可以将一系列操作打包成原子来执行,以此来规避线程不安全。
具体操作
加锁
关键字:synchronized
可以修饰方法,可以修饰代码块。
但同时我们要注意,当多个线程对同一变量进行修改操作时,每个线程执行的操作中都要对其进行加锁,如果线程没有全部加锁,那么也是徒劳的,依然会引发线程不安全的问题。
1. 当我们对方法进行加锁时:
public synchronized void increase() {
num++;
}
2.对想要加锁的代码进行加锁
我们这里填入this就好
public void increase() { synchronized (this) { num++; } }
3.当不完全加锁时
输出的num 会重新变成随机数 因此我们都要对其进行加锁 class Count { public int num = 0; public void increase() { synchronized (this) { num++; } } public void add() { num++; } }
public class test { public static void main(String[] args) throws InterruptedException { Count count = new Count(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1_0000; i++) { count.increase(); } }) ; Thread t2 = new Thread(() -> { for (int i = 0; i < 1_0000; i++) { count.add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("num: " + count.num); } }
本文含有隐藏内容,请 开通VIP 后查看