最近在编写项目的时候使用while遇到了一个奇怪的问题。我在使用异步调用的时候主线程某一个方法需要等待异步返回才能被调用,因此我设定了一个boolean,当异步返回时修改条件然后在主线程的方法中加入while来长时间遍历以等待异步返回。
这里我将代码省略只保留主要:
public static void main(String[] args) { Task task = new Task(); new Thread(task).start(); try { Thread.sleep(3000); } catch (Exception e) {} task.close();}class Task implements Runnable { private int i = 1; public void run() { while (i == 1){} System.out.println("跳出循环"); } public void close() { i = 2; }}复制代码
Task启动后会在while中死循环,主线程等待3s后将i修改成2,但是task中的while没有跳出,即 i == 1 条件还是为true。
其中的while换成for,do-while都是一样的结果
通过询问他人,虽然没有弄明白发生的原因。但是他提出了一个解决办法。
在程序中
while(i == 1){}复制代码
会过多占用CPU,因此使用Thread.yield()来将CPU资源让步给其他线程。当while中加入这个之后就能达到我需要的效果了。
很奇怪
更新: Java内存模型规定:
- 共享变量必须保存在主内存中
- 线程有自己的工作内存,线程只可以操作自己的工作内存
- 线程要操作共享变量,需要从主内存中读取到工作内存,修改后需从工作内存同步到主内存中。
这三点直接就点名了错误的原因。解决办法有两个:
-
volatile关键字:
volatile语意:- 使用volatile变量时,必须重新从主内存加载,并read、load是连续的。
- 修改volatile变量后,必须立马同步回主内存,并且store、write是连续的。
缺点:volatile只能保证线程的变量可见性。但是它没有锁机制,所以无法避免多个线程同时访问公共变量。
优点:编写简单。
-
synchronized关键字
synchronized语意:- 进入同步块前,先清空工作内存的共享变量,再从主内存重新加载,同时获取该共享资源的锁。
- 修改后必须先将共享变量同步回主内存中才能释放锁。
优点:有加锁机制,保护共享资源。
补充 - 内存协议:
Java内存协议规定了8中原子操作:
- lock(锁定):将主内存的变量锁定,为一线个线程独占。
- unlock(解锁):将lock加的锁解除。
- read(读取):作用于主内存变量,将主内存的变量放入寄存器中。
- load(载入):作用于工作内存,将寄存器中的主内存变量传递给线程的工作内存。
- use(使用):作用于工作内存,将值传递给线程的代码执行引擎。
- assign(赋值):作用于工作内存,将执行引擎处理返回的值重新赋值给寄存器。
- store(存入):将寄存器中的变量传入主内存中。
- write(写入):作用于主内存变量,将store传过来的值写入到主内存的共享变量中。
这些操作都是原子性,但是操作之间不是原子性。