欢迎关注个人主页:逸狼
创造不易,可以点点赞吗~
如有错误,欢迎指出~
目录
线程安全的 第四个原因
代码举例:
分析原因
解决方法
方法1
方法2
wait(等待)和notify(通知)
wait和sleep区别
线程安全的 第四个原因
内存可见性,引起的线程安全问题
比如: 一个线程修改,另一个线程读取
代码举例:
import java.util.Scanner;
public class Demo13 {
public static int n=0;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while(n==0){
//啥也不写
}
System.out.println("t1线程结束循环");
});
Thread t2 = new Thread(()->{
Scanner scanner =new Scanner(System.in);
System.out.println("请输入一个整数: ");
n= scanner.nextInt();
});
t1.start();
t2.start();
}
}
上述代码中通过线程t2将n的值修改成 非0值,按代码逻辑t1应该结束循环了,但实际上循环还在继续...
分析原因
内存可见性问题,本质上是编译器/ JVM对代码进行优化的时候,优化出了bug,如果代码是单线程的,编译器的代码优化一般都是非常准确的,优化之后不会影响到逻辑
但是代码如果是多线程的,编译器的优化就可能出现误判,导致不该优化的地方也给优化了
解决方法
方法1
加上sleep,增加开销,让编译器不启用优化
方法2
在变量n的前面 加上volatil关键字(volatil 修饰一个变量,提示编译器这个变量是"易变的")
编译器进行上述优化的前提 是编译器认为,针对这个变量的频繁读取,结果都是固定的
但是volatile 只能解决内存可见性问题,不能解决原子性问题(如果两个线程针对同一个变量进行修改,volatile无能为力)
wait(等待)和notify(通知)
多给线程需要控制线程之间 执行某个逻辑的先后顺序,可以使用wait让 后执行的逻辑等待,完成某些逻辑之后 通过notify唤醒对应的wait
通过wait和notify可以解决'线程饿死'问题
- wait包含 三个操作:解锁和阻塞等待(这两个操作同时进行(在内部已经打包成原子的),阻塞就是为了收到通知),接收到通知后唤醒,并且重新尝试获取锁
- notify 是通知wait的线程被唤醒(使用 另一个线程调用)
import java.util.Scanner;
public class Demo21 {
private static Object locker = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("t1 wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1 wait 之后");
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2 notify 之前");
Scanner scanner = new Scanner(System.in);
scanner.next(); // 此处用户输入啥都行, 主要是通过这个 next, 构造 "阻塞"
synchronized (locker) {
locker.notify();
}
System.out.println("t2 notify 之后");
});
t1.start();
t2.start();
}
}
在多线程中,一个线程加锁,另一个线程加锁是无意义的,不会有任何阻塞效果
wait和notify使用之前都需要确保 先加锁(都需要搭配synchronized使用),才能执行
- wait默认是"死等"(如果没有notify通知,就会一直等待)
- wait还提供带参数的版本,指定最大时间(如果wait达到了最大的时间,还没有notify,就不会继续等待了,而是直接继续执行)
wait和sleep区别
假如 多个线程都在同一个对象上wait,此时notify 会随机 唤醒其中的一个线程,而notifyAll会唤醒所有等待的线程 ,大部分情况使用notify一个一个唤醒(多次执行notify),目的是 整个程序执行的过程是比较有序的,如果一下全部唤醒,这些被唤醒的线程会 无序的竞争锁
如果 notify 通知时,无线程wait,不会有任何副作用.