一.定义:
一段代码,在多线程中,并发执行,出现bug的情况。
二.原因:
1)操作系统对于线程的调度是随机的,抢占式执行。
2)多个线程修改同一个变量。
3)线程的修改操作并不是原子的(也就是线程执行时调度了其他线程)。
4)内存可见性问题->编译器自动优化
5)指令重排序。
三.解决方案:
由于1是操作系统的特性,所以无法从这个方面来优化问题
1)加锁:关键字synchronized,就可以解决(3)类问题:
java
Object locker1 = new Object();
Object locker = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
对于java来说,因为锁的可重性,在同一个线程加锁中再加锁,只要锁对象相同那么就可以:
java
public static void main(String[] args) throws InterruptedException {
Counter2 counter = new Counter2();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (counter) {
synchronized (counter) {
synchronized (counter) {
counter.add();
}
}
}
}
});
t1.start();
t1.join();
System.out.println("count = " + counter.get());
}
除此以外,synchronized还可以修饰方法,括号内要填入一个锁对象,可以是Object本身或者任意子类,那么加锁有好处,那么就会有一些新问题。
1.1)死锁:死锁会是程序卡住,从而无法往后执行
1.1.2)死锁形成原因:
1)锁与锁之间是互斥的,若想要使用同一个锁对象必须等待上一个锁 解锁并释放锁对象。
2)锁的不可剥夺\抢占的特点
3)锁的请求和保持:若锁处于阻塞状态,那么它就会一直等待和请求
java
public static void main(String[] args) throws InterruptedException {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker2) {
System.out.println("t1 线程两个锁都获取到");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker2) {
System.out.println("t2 线程两个锁都获取到");
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
4)循环等待:多把锁需要多个锁对象,从而相互等待的情况。
1.1.3)死锁的解决办法:
1)避免锁嵌套,尽量使用一共锁对象=>打破3);
2)约定加锁顺序=>打破4);
2)volatile: 用此关键词来修饰变量那么就等于给编译器表明,此变量是"易失"的。
对于代码:
java
package TreadDemo;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: czt20
* Date: 2026 -05-16
* Time: 15:36
*/
public class Demo20 {
static int k = 0;
public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Thread t1 = new Thread(()->{
synchronized (a){
System.out.println("需要输入");
while(k == 0){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1线程被执行");
}
});
Thread t2 = new Thread(()->{
synchronized (b){
Scanner scanner = new Scanner(System.in);
k = scanner.nextInt();
System.out.println("t2线程被执行");
}
});
t1.start();
t2.start();
}
}
因为cpu在while循环中执行速度非常快,在我们输入之前已经执行了无数次,无数次k的结果都是0那么编译器自动将k从寄存器读取内存上的k值这一步省略,导致后续输入k的值,无济于事。那么volatile的修饰就是来阻止这种编译器优化的情况发生。