4.1 观察线程不安全
运行以下代码:
package demo02;
public class Test {
private static int count = 0;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50_000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50_000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count: " + count);
}
}
运行之后每次的结果 count 都不一样 且不为100000
4.2 线程不安全的原因
什么是原⼦性?
我们把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊ 房间之后,还没有出来;B是不是也可以进⼊房间,打断A在房间⾥的隐私。这个就是不具备原⼦性的。 那我们应该如何解决这个问题呢?是不是只要给房间加⼀把锁,A进去就把⻔锁上,其他⼈是不是就进 不来了。这样就保证了这段代码的原⼦性了。 有时也把这个现象叫做同步互斥,表⽰操作是互相排斥的。
4.2.1 对count++进行分析:
操作系统对于线程的调度,是随机的
执行123 三个指令的时候,不一定是 一下子执行完
可能是执行到其中一部分,该线程就被调度走了
因此,线程调度是随机的 这是线程安全问题的 罪魁祸⾸
正确执行示意图:
错误执行示意图:
4.3线程不安全原因与解决小结
原因:
1.[根本原因] 操作系统上的线程是“抢占式执行”“随机调度”=>线程之间执行的顺序带来了很多变数(罪魁祸首,万恶之源)
2.代码结构.代码中多个线程,同时修改同一个变量,
1)一个线程修改一个变量,没事的.
2)多个线程读取同一个变量,没事的. 如果只是读取,变量的内容,固定不变的
3)多个线程修改不同变量,没事的~~ 如果是两个不同变量
彼此之间就不会产生相互覆盖的情况了.
3.[直接原因】上述多线程修改操作,本身不是“原子的"
解决:
针对原因1,无法做出任何改变.系统内部已经实现了抢占式执行,无法干预~~
针对原因2,分情况.有的时候,代码结构可以调整,有的时候,调整不了。
针对原因3,乍看起来,count++,生成几个指令,无从干预~~但是,实际上是有办法的~~
可以通过特殊手段,把这三个指令打包到一起,成为“整体” --> 加锁 锁具有“互斥”“排他”这样的特性
4.4 解决之前的线程不安全问题
使用synchronized 进行加锁操作
public class demo01 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50_000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50_000; i++) {
synchronized (locker) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " + count);
}
}