volatile
import java.util.Scanner;
public class ThreadDemo {
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread() {
@Override
public void run() {
while (counter.flag == 0) {
}
System.out.println("循环结束");
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("亲输入一个整数");
counter.flag = scanner.nextInt();
}
};
t2.start();
}
}
这个代码本来的预期效果是输入整数后循环结束,但是当输入后,并没有结束。
其实,这是编译器优化的问题。开始,线程1 反复对循环条件进行比较操作(先从内存中读取flag的值到CPU,在CPU中比较这个值和0的相等关系),因为从CPU寄存器上读取数据比内存中读数据还是快很多的,编译器判定这个判断相等操作就是不断从内存中读取内存而已,于是编译器就进行了优化,在第一次吧数据从内存读取到CPU后,之后就直接从CPU中读取数据,所以编译器认为flag没有改动,就出现了误判。
这样的优化策略就是“内存可见性”;
如果优化生效,内存不可见,不生效,内存才是可见的。
但是当在flag前加上volatile后,就可以保证内存可见性了。
但这个和原子性是没有关系的,对于一个线程读,一个线程写,得用volatile解决,但是对于两个线程修改同一变量时,还得用加锁去解决。
notify和wait
对象等待集 (本质让程序员有一定手段去干预线程调度)
主要还是因为抢占式执行的问题
wait方法:当操作条件不成熟就等待(必须在sychronized内部使用)
notify方法:当条件成熟,通知指定线程来工作。(也必须在sychronized里使用)
wait步骤:
1.释放锁
2.等待通知
3.收到通知,重新获取锁,继续往下执行
其中,前两步在wait中是原子的,避免竞态条件问题。
调用锁的对象和wait的对象必须是对应的。
import java.util.Scanner;
public class ThreadDemo4 {
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait开始");
locker.wait();
System.out.println("wait结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("输入整数");
int num = scanner.nextInt();
synchronized (locker) {
System.out.println("notify开始");
locker.notify();
System.out.println("notify结束");
}
}
};
t2.start();
}
}