# CPUCacheTest 更多的讨论
CPUCacheTest 经常作为大厂的面试题,难倒很多精通多线程并发的面试者,这里列出这个题目尽可能多的答案。综合考虑了变量可见性,线程上下文切换,以及JIT优化。
如下一段程序,期望当主线程A设置stop变量为true的时候,线程B退出循环。 但实际上线程B看不到更新后的值,从而一直循环下去。
~~~java
public class CPUCacheTest {
private static boolean stop = false;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
int a = 1;
}
System.out.println("exit");
}
};
a.start();
pause(100);
//停止标记,但未能停止线程B
stop = true;
}
public static void pause(int time){
try {
TimeUnit.MILLISECONDS.sleep(time);
}catch(Exception ex){
}
}
}
~~~
## 原因分析
在第三章已经说过,多线程下变量的可见性,需要添加`volatile` 关键字,保证stop变量能被其他线程看到
```java
private static volatile boolean stop = false;
```
## 场景一
判断如下代码是否也能保证线程B退出
```java
Thread a = new Thread("B"){
public void run(){
while (!stop) {
System.out.println("in loop");
}
System.out.println("exit");
}
};
```
答案,能退出,因为方法`out.println()`实际上如下实现,synchronized保证变量可见性
```java
private void write(String s) {
synchronized (this) {
.....
}
}
```
## 场景二
判断如下代码是否也能保证线程B退出,调用pause方法,休眠1毫秒
```java
Thread a = new Thread("B"){
public void run(){
while (!stop) {
pause(1);
}
System.out.println("exit");
}
};
```
答案,能退出,pause方法引起线程休眠,再次唤醒的时候,上下文切换,线程B能得到stop最新的变量
## 场景三
判断如下代码是否也能保证线程B退出,定义int类型的变量b,在循环里自增
```
public class CPUCacheTest {
private static /* volatile */ boolean stop = false;
static int b = 1;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
b++
}
System.out.println("exit "+b);
}
};
System.out.println("start");
a.start();
pause(100);
stop = true;
}
```
答案,不能退出,没有任何原因触发变量可见性
## 场景四
判断如下代码是否也能保证线程B退出,定义Integer类型的变量b,在循环里自增
```java
public class CPUCacheTest {
private static /* volatile */ boolean stop = false;
static Integer b = 1;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
b++
}
System.out.println("exit "+b);
}
};
System.out.println("start");
a.start();
pause(100);
stop = true;
}
```
答案,能退出,因为如上代码`b++` 实际的操作如下。
```java
int temp = b.intValue();
temp++;
b = new Integer(temp);
```
因为Integer定义如下,因为变量是final,保证了变量可见性
```java
private final int value;
public Integer(int value) {
this.value = value;
}
```
## 场景五
判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量,并自增
```java
public class CPUCacheTest {
private static /* volatile */ boolean stop = false;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
Integer c = 1;
c++;
}
System.out.println("exit "+b);
}
};
System.out.println("start");
a.start();
pause(100);
stop = true;
}
```
答案,不能退出,在while循环里,变量c的操作被JIT优化成如下代码
```java
Thread a = new Thread("B"){
public void run(){
while (!stop) {
//代码被消除
}
System.out.println("exit "+b);
}
};
```
## 场景六
判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量c,自增后赋值给b
```java
public class CPUCacheTest {
private static /* volatile */ boolean stop = false;
static int b = 1;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
Integer c = 1;
c++;
b=c;
}
System.out.println("exit "+b);
}
};
System.out.println("start");
a.start();
pause(100);
stop = true;
}
```
答案,不能退出,变量c 赋值到b操作会提取到循环外执行,这涉及到JIT编译器优化,表达式提升(hoisting),运行代码如下
```java
Thread a = new Thread("B"){
public void run(){
Integer c = 1;
c++;
b=c;
while (!stop) {
}
System.out.println("exit "+b);
}
};
```
## 场景七
判断如下代码是否也能保证线程B退出,while循环里使用StringBuffer
```java
Thread a = new Thread("B"){
public void run(){
while (!stop) {
StringBuffer sb = new StringBuffer();
sb.append("abc");
}
System.out.println("exit "+b);
}
};
```
答案,能退出,StringBuffer 的append方法有关键字synchronzied
```java
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
```
## 场景八
同场景3,但运行这个程序加上虚拟机参数 -Xint ,程序能正确退出么?
```java
public class CPUCacheTest {
private static /* volatile */ boolean stop = false;
static int b = 1;
public static void main(String[] args){
Thread a = new Thread("B"){
public void run(){
while (!stop) {
b++
}
System.out.println("exit "+b);
}
};
System.out.println("start");
a.start();
pause(100);
stop = true;
}
```
在第8章JIT说过,-Xint 是让Java解释执行,这样,不会涉及到变量可见性,因此无论while循环里是什么,都能退出
- 内容介绍
- 第一章 Java系统优化
- 1.1 可优化的代码
- 1.2 性能监控
- 1.3 JMH
- 1.3.1 使用JMH
- 1.3.2 JMH常用设置
- 1.3.3 注意事项
- 1.3.4 单元测试
- 第二章 字符串和数字
- 2 字符串和数字操作
- 2.1 构造字符串
- 2.2 字符串拼接
- 2.3 字符串格式化
- 2.4 字符串查找
- 2.6 intern方法
- 2.7 UUID
- 2.8 StringUtils类
- 2.9 前缀树过滤
- 2.10 数字装箱
- 2.11 BigDecimal
- 第三章 并发和异步编程
- 3.1 不安全的代码
- 3.2 Java并发编程
- 3.2.1 volatile
- 3.2.2 synchronized
- 3.2.3 Lock
- 3.2.4 Condition
- 3.2.5 读写锁
- 3.2.6 semaphore
- 3.2.7 栅栏
- 3.3 Java并发工具
- 3.3.1 原子变量
- 3.3.2 Queue
- 3.3.3 Future
- 3.4 Java线程池
- 3.5 异步编程
- 3.5.1 创建异步任务
- 3.5.2 完成时回调
- 3.5.3 串行执行
- 3.5.4 并行执行
- 3.5.5 接收任务处理结果
- 第四章 代码性能优化
- 4.1 int 转 String
- 4.2 使用Native 方法
- 4.3 日期格式化
- 4.4 switch 优化
- 4.5 优先用局部变量
- 4.6 预处理
- 4.7 预分配
- 4.8 预编译
- 4.9 预先编码
- 4.10 谨慎使用Exception
- 4.11 批处理
- 4.12 展开循环
- 4.13 静态方法调用
- 4.14 高速Map存取
- 4.15 位运算
- 4.16 反射
- 4.17 压缩
- 4.18 可变数组
- 4.19 System.nanoTime()
- 4.20 ThreadLocalRandom
- 4.21 Base64
- 4.22 辨别重量级对象
- 4.23 池化技术
- 4.24 实现hashCode
- 4.25 错误优化策略
- 4.25.1 final无法帮助内联
- 4.25.2 subString 内存泄露
- 4.25.3 循环优化
- 4.25.4 循环中捕捉异常
- 4.25.5 StringBuffer性能不如StringBuilder高
- 第五章 高性能工具
- 5.1 高速缓存 caffeine
- 5.1.1 安装
- 5.1.2 caffeine 基本使用
- 5.1.3 淘汰策略
- 5.1.4 statistics 功能
- 5.1.5 caffeine高命中率
- 5.1.6 卓越性能
- 5.2 selma映射工具
- 5.3 Json工具 Jackson
- 5.3.1 Jackson三种使用方式
- 5.3.3 对象绑定
- 5.3.2 Jackson 树遍历
- 5.3.4 流式操作
- 5.3.6 自定义 JsonSerializer
- 5.3.7 集合的反序列化
- 5.3.8 性能提升和优化
- 5.4 HikariCP
- 5.4.1 HikariCP安装
- 5.4.3 HikariCP 性能测试
- 5.4.4 性能优化说明
- 5.5 文本处理Beetl
- 5.5.1 安装和配置
- 5.5.2 脚本引擎
- 5.5.3 特点
- 5.5.4 性能优化
- 5.6 MessagePack
- 5.7 ReflectASM
- 第六章 Java注释
- 6.1 JavaDoc
- 6.2 Tag
- 6.2.1 {@link}
- 6.2.2 @deprecated
- 6.2.3 {@literal}
- 6.2.4 {@code}
- 6.2.5 {@value}
- 6.2.6 @author
- 6.2.7 @param 和 @return
- 6.2.8 @throws
- 6.2.9 @see
- 6.2.10 自动拷贝
- 6.3 Package-Info
- 6.4 HTML生成
- 6.5 Markdown-doclet
- 第七章 可读性代码
- 7.1 精简注释
- 7.2 变量
- 7.2.1 变量命名
- 7.2.2 变量的位置
- 7.2.3 中间变量
- 7.3 方法
- 7.3 .1 方法签名
- 7.3.2 小方法
- 7.3.3 单一职责
- 7.3.3 小类
- 7.4 分支
- 7.4.1 if else
- 7.4.2 switch case
- 7.5 发现对象
- 7.3.1 不要用String
- 7.3.2 不要用数组,Map
- 7.6 checked异常
- 7.7 其他
- 7.7.1 避免自动格式化
- 7.7.2 关于Null
- 第八章 JIT优化
- 8.1 解释和编译
- 8.2 C1和C2
- 8.3 代码缓存
- 8.4 JITWatch
- 8.5 内联
- 8.6 虚方法调用
- 第九章 代码审查
- 9.1 ConcurrentHashMap陷阱
- 9.2 字符串搜索
- 9.3 IO输出
- 9.4 字符串拼接
- 9.5 方法的入参和出参
- 9.6 RPC调用定义的返回值
- 9.7 Integer使用
- 9.8 排序
- 9.9 判断特殊的ID
- 9.10 优化if结构
- 9.11 文件COPY
- 9.12 siwtch优化
- 9.13 Encoder
- 9.14一个JMH例子
- 9.15 注释
- 9.16 完善注释
- 9.17 方法抽取
- 9.18 遍历Map
- 9.19 日期格式化
- 9.20 日志框架设计的问题
- 9.21 持久化到数据库
- 9.22 某个RPC框架
- 9.23 循环调用
- 9.24 Lock使用
- 9.25 字符集
- 9.26 处理枚举值
- 9.27 任务执行
- 9.28 开关判断
- 9.29 JDBC操作
- 9.30 Controller代码
- 9.31 停止任务
- 9.32 log框架
- 9.33 缩短UUID
- 9.34 Dubbo ThreadPool设置
- 9.35 压缩设备信息
- 第十章 ASM运行时增强
- 10.1 Java字节码
- 10.1.1 基础知识
- 10.1.2 class文件格式
- 10.2 Java方法的执行
- 10.2.1 方法在内存的表示
- 10.2.3 方法在class文件中的表示
- 10.2.3 指令的分类
- 10.2.4 操作数栈的变化分析
- 10.3 Bytecode Outline插件
- 10.4 ASM入门
- 10.4.1 生成类名及构造函数
- 10.4.2 生成main方法
- 10.4.3 调用生成的代码
- 10.5 ASM增强代码
- 10.5.1 使用反射实现
- 10.5.2 使用ASM生成辅助类
- 10.5.3 switch语句的分类
- 10.5.4 获取bean中的property
- 10.5.5 switch语句的实现
- 10.5.6 性能对比
- 第十一章 JSR269编译时增强
- 11.1 Java编译的过程
- 11.2 注解处理器入门
- 11.3 相关概念介绍
- 11.3.1 AbstractProcessor
- 11.3.2 Element与TypeMirror
- 11.4 注解处理器进阶
- 11.4.1 JsonWriter注解
- 11.4.2 处理器与生成辅助类
- 11.4.3 使用生成的Mapper类
- 11.4.4 注解处理器的使用
- 11.5 调试注解处理器
- 11.5.1 Eclipse中调试注解处理器
- 11.5.2 Idea中调试注解处理
- 附录A OQL分析JVM内存(免费)
- 附录B 7行代码的9种性能优化方法(免费)
- 附录 C CPUCacheTest更多讨论(免费)