本篇重点: 总结线程安全问题的原因以及解决办法
目录
- synchronized 加锁关键字
- join 和 synchronized 的区别
- volatile 关键字
在上一篇中我们介绍了Thread类的基本使用方法, 本篇将会介绍有关于线程的安全问题
线程不安全的原因:
抢占式执行(罪魁祸首, 万恶之源)
多个线程修改同一个变量
一个线程修改同一个变量 => 安全 多个线程读取同一个变量 => 安全 多个线程修改不同变量 => 安全
修改操作并不是原子的
像是++操作就不是原子操作
内存可见性, 引起的线程不安全
指令重排序, 引起的线程不安全
线程不安全代码案例
使用两个线程来累加 count 的值, 每个线程循环 1w 次,累加变量 count 的值,count 默认值为 0,注意线程安全问题。
class Counter{
private int count;
public void add() {
count++;
}
public int getCount() {
return count;
}
}
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 搞两个线程各自自增5w次看结果
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.add();
}
});
t1.start();
t2.start();
// 等待两个线程执行结束, 然后看结果
t1.join();
t2.join();
System.out.println(counter.getCount());
}
}
因为count++操作并不是原子性的所以线程并不安全
如何解决以上线程不安全问题???
synchronized 加锁关键字
加锁!!!
对count++ 进行加锁
此处使用代码块的方式来表示
进入synchronized 修饰的代码块的时候, 就会触发加锁 出了synchronized 代码块, 就会触发解锁
public void add() {
synchronized (this){// this 指向的是count对象
count++;
}
}
也可以在方法前加上synchronized关键字
synchronized public void add() {
count++;
}
join 和 synchronized 的区别
join只是让两个线程完整的进行串行
加锁, 两个线程的某个小部分串行了, 大部分都是并发的!!!
volatile 关键字
Volatile关键字的作用主要有如下两个:
1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
2. 顺序一致性:禁止指令重排序。
被volatile 优化的关键字,会禁止编译器进行优化
当循环次数下降了, load操作, 不再是负担了, 编译器就没必要优化了
指令重排序, 也是编译器优化的策略
public class ThreadDemo14 {
volatile public static int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (flag == 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("循环结束, t1线程结束");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数: ");
flag = scanner.nextInt();
});
t1.start();
t2.start();
}
}