这恐怕是Java面试最变态的题,没有之一

原创
2021/12/16 19:19
阅读数 3.1K

本资料来源Java性能优化

变量可见性经常作为大厂的面试题,难倒很多精通多线程并发的面试者,这里列出这个题目尽可能多的答案。综合考虑了变量可见性,线程上下文切换,以及JIT优化。

如下一段程序,期望当主线程A设置stop变量为true的时候,线程B退出循环。 但实际上线程B看不到更新后的值,从而一直循环下去。

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变量能被其他线程看到

private static volatile boolean stop = false;

场景一

判断如下代码是否也能保证线程B退出

Thread a = new Thread("B"){
      public void run(){
        while (!stop) {
           System.out.println("in loop");
        }
        System.out.println("exit");
      }
    };

答案,能退出,因为方法out.println()实际上如下实现,synchronized保证变量可见性

private void write(String s) {
  synchronized (this) {
		.....
  }
}

场景二

判断如下代码是否也能保证线程B退出,调用pause方法,休眠1毫秒

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,在循环里自增

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++ 实际的操作如下。

int temp = b.intValue();
temp++;
b = new Integer(temp);

因为Integer定义如下,因为变量是final,保证了变量可见性


    private final int value;
    public Integer(int value) {
        this.value = value;
    }

场景五

判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量,并自增

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优化程如下代码

Thread a = new Thread("B"){
		  public void run(){
			  while (!stop) {
				//代码被消除
			  }
			System.out.println("exit "+b);
		  }
};

场景六

判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量c,自增后赋值给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) {
                    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),运行代码如下

Thread a = new Thread("B"){
    public void run(){
        Integer c = 1;
        c++;
        b=c;
        while (!stop) {

        }
        System.out.println("exit "+b);
    }
};

场景七

判断如下代码是否也能保证线程B退出,while循环里使用StringBuffer

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

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

场景八

同场景3,但运行这个程序加上虚拟机参数  -Xint ,程序能正确退出么?

答案 在第8章JIT说过,-Xint 是让Java解释执行,这样,不会涉及到变量可见性,因此无论while循环里是什么,都能退出

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
2 收藏
4
分享
返回顶部
顶部