目录
1. 抢占式执行(狼多肉少)
2. 多个线程修改同一个变量
3. 非原子性操作
4. 内存可见性
5. 指令重排序
线程不安全指的是程序在多线程的执行结果不符合预期。
单线程:
public class ThreadDemoCounter2 {
static class Counter {
private int num = 0;
public void incr(int count) {
for (int i = 0; i < count; i++) {
num++;
}
}
public void decr(int count) {
for (int i = 0; i < count; i++) {
num--;
}
}
public int getNum() {
return num;
}
}
public static void main(String[] args) {
int count = 100000;
Counter counter = new Counter();
counter.incr(count);
counter.decr(count);
System.out.println(counter.getNum());
}
}
输出:
0
多线程:
public class ThreadDemoCounter3 {
static class Counter {
private int num = 0;
public void incr(int count) {
for (int i = 0; i < count; i++) {
num++;
}
}
public void decr(int count) {
for (int i = 0; i < count; i++) {
num--;
}
}
public int getNum() {
return num;
}
}
public static void main(String[] args) throws InterruptedException {
int count = 10000;
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
counter.incr(count);
});
t1.start();
Thread t2 = new Thread(() -> {
counter.decr(count);
});
t2.start();
t1.join();
t2.join();
System.out.println(counter.getNum());
}
}
输出:
最终结果:0
1. 抢占式执行(狼多肉少)
2. 多个线程修改同一个变量
public class ThreadDemoCounter4 {
static class Counter {
int num = 0;
public int incr(int count) {
for (int i = 0; i < count; i++) {
num++;
}
return num;
}
public int decr(int count) {
for (int i = 0; i < count; i++) {
num--;
}
return num;
}
}
public static void main(String[] args) throws InterruptedException {
int count = 10000;
Counter counter = new Counter();
final int[] num1 = new int[1];
Thread t1 = new Thread(() -> {
num1[0] = counter.incr(count);
});
t1.start();
final int[] num2 = new int[1];
Thread t2 = new Thread(() -> {
num2[0] = counter.decr(count);
});
t2.start();
t1.join();
t2.join();
System.out.println(num1[0] + num2[0]);
}
}
输出:
100000
3. 非原子性操作
给房间加?把锁,A 进去就把?锁上,其他?就进不来,这样就保证了代码的原?性。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
?条 java 语句不?定是原?的,也不?定只是?条指令。
num++ 实际上是由散步操作组成的:
? ? ? ? a . 从内存把数据读到 CPU
????????b.?进?数据更新
????????c.?
把数据写回到 CPU
不保证原?性:
如果?个线程正在对?个变量操作,中途其他线程插?进来了,如果这个操作被打断了,结果就可能是
错误的。
public class ThreadDemoCounter5 {
static class Counter {
private int num = 0;
public void incr(int count) {
for (int i = 0; i < count; i++) {
num++;
}
}
public void decr(int count) {
for (int i = 0; i < count; i++) {
num--;
}
}
public int getNum() {
return num;
}
}
public static void main(String[] args) throws InterruptedException {
int count = 10000;
ThreadDemoCounter3.Counter counter = new ThreadDemoCounter3.Counter();
Thread t1 = new Thread(() -> {
counter.incr(count);
});
t1.start();
t1.join();
Thread t2 = new Thread(() -> {
counter.decr(count);
});
t2.start();
t2.join();
System.out.println(counter.getNum());
}
}
输出
0
?
4. 内存可见性
即就是,?个线程对共享变量值的修改,能够及时地被其他线程看到。
public class ThreadDemoCounter6 {
private static boolean flag = true;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程1:开始执行" + LocalDateTime.now());
while(flag ) {
}
System.out.println("线程1:结束执行" + LocalDateTime.now());
});
t1.start();
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:修改flag = false" + LocalDateTime.now());
flag = false;
});
t2.start();
}
}
输出:
线程1:开始执行2022-03-29T09:20:29.894
线程2:修改flag = false2022-03-29T09:20:30.856
可以发现·,线程2修改全局变量之后,理论上,线程1应该结束,并输出“线程1:结束执行 + 时间”,但线程1却在一直在执行,并没有感知到全局变量flag的变化,这就是内存可见性问题。
Java 内存模型 (JMM):?
?a.?线程之间的共享变量存在 主内存
b.?每?个线程都有??的 "?作内存"
c.?当线程要读取?个共享变量的时, 会先把变量从主内存拷?到?作内存, 再从?作内存读取数据.
d.?当线程要修改?个共享变量的时候, 也会先修改?作内存中的副本, 再同步回主内存.
5. 指令重排序
为了提供性能,编译器和处理器会在保证逻辑不变的情况下,对指令做重排序。但这个优化非常地复杂。
编译器优化的本质是调整代码的执?顺序,在单线程下没问题,但在多线程下容易出现混乱,从?造成线程安全问题。
|