Java性能调优面试-4

1、Volatile可见性底层实现原理

代码分析:

通过循环开启10个线程,每个线程执行1000次increase()。

t.join()表示主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

即通过循环表示每调一次t.join()表示主线程等该子线程结束,循环结束后则所有子线程执行完成,则主线程继续想下执行。

public class VolatileAtomicTest {

    public static volatile int num = 0;

    public static void increase(){
        num++;
    }

    public static void main(String[] args) throws InterruptedException {

        Thread[] threads = new Thread[10];
        for(int i=0;i<threads.length;i++){
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<1000;i++){
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        //主线程需要等待(阻塞)所有子线程执行完
        for (Thread t : threads){
            t.join();
        }
        System.out.println(num);//1000*100=10000
    }
}

结果中num<=10000。

结果分析:

num++不保证原子性:是因为+1的操作有丢失。

结果<10000的原因:当线程1计算完num++执行assign得num=1,此时线程1准备将num=1回写回主内存。在线程1写到总线之前,线程2也计算完num++执行assign得num=1。线程1开始经过总线,而线程2通过CPU总线嗅探会将工作内存值失效,此时导致线程2的计算结果失效。当线程1更新回主内存时num=0>1。此时主内存值为1。

正常线程1将数据同步回主内存,线程2开始读主内存执行计算逻辑,则num=1>2。

2、Volatile有序性

2.1、没有添加volatile

public class VolatileSerialTest {

    static int x=0,y=0;

    public static void main(String[] args) throws InterruptedException {
        Set<String> resultSet = new HashSet<>();
        Map<String, Integer> resultMap = new HashMap<>();
        for (int i = 0; i < 1000000; i++) {
            x=0;y=0;
            resultMap.clear();
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    int a = y;
                    x = 1;
                    resultMap.put("a", a);
                }
            });

            Thread other = new Thread(new Runnable() {
                @Override
                public void run() {
                    int b = x;
                    y = 1;
                    resultMap.put("b", b);
                }
            });

            one.start();
            other.start();
            one.join();
            other.join();
            resultSet.add("a="+resultMap.get("a")+","+"b="+resultMap.get("b"));
            System.out.println(resultSet);
        }
    }

}

结果:[a=1,b=0, a=0,b=1,a=0,b=0, a=1,b=1]

2.2、添加volatile

public class VolatileSerialTest {

    static volatile int x=0,y=0;

    public static void main(String[] args) throws InterruptedException {
        Set<String> resultSet = new HashSet<>();
        Map<String, Integer> resultMap = new HashMap<>();
        for (int i = 0; i < 1000000; i++) {
            x=0;y=0;
            resultMap.clear();
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    int a = y;
                    x = 1;
                    resultMap.put("a", a);
                }
            });

            Thread other = new Thread(new Runnable() {
                @Override
                public void run() {
                    int b = x;
                    y = 1;
                    resultMap.put("b", b);
                }
            });

            one.start();
            other.start();
            one.join();
            other.join();
            resultSet.add("a="+resultMap.get("a")+","+"b="+resultMap.get("b"));
            System.out.println(resultSet);
        }
    }

}

结果:[a=1,b=0, a=0,b=1,a=0,b=0]

2.3、原因分析

程序存在指令重排,导致下面四行代码执行的先后顺序不确定,都有可能。

 int a = y;
 x = 1;
 int b = x;
 y = 1;

存在volatile时,volatile修饰了x和y,而lock指令修饰的代码自带内存屏障的功能,可以理解为凡是有内存屏障修饰的指令,cpu是没有办法把它前面的代码优化排到它的后面执行。即x=1和y=1前面都是有lock修饰的,cpu不能把x=1前面额所有代码拿到x=1后面执行,也不能把y=1前面的所有代码拿到y=1后面执行。

cpu对代码重排序的原因:cpu内部认为重排序后比排序前代码执行效率更高,往往cpu重排序会导致bug产生。

2、t.join()和t.wait()

join()的使用场景

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将可能早于子线程结束。如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束了。

主线程可以sleep(xx),但这样的xx时间不好确定,因为子线程的执行时间不确定,join()方法比较合适这个场景。

join()方法:

join()是Thread类的一个方法。根据jdk文档的定义:

public final void join()throws InterruptedException: Waits for this thread to die.

join()方法的作用,是等待这个线程结束;

即是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

 

 

 

 

参考:https://www.cnblogs.com/duanxz/p/5038471.html

https://nachifur.blog.csdn.net/article/details/58315357?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-7.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-7.control

https://xiaochengxinyizhan.blog.csdn.net/article/details/79787735?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-1.control

 

### Java 性能面试题整理 #### 1. JVM 基础知识 - **什么是 Full GC?如何减少 Full GC 的发生频率?** 减少 Full GC 发生的关键在于合理配置堆内存大小以及整新生代与老年代的比例。可以通过设置 `-Xmn` 参数来增大新生代的空间,从而降低对象晋升到老年代的速度[^5]。 - **JVM 中的程序计数器是什么?它的作用有哪些?** 程序计数器是一个较小的内存区域,用于记录当前线程所执行字节码的地址。如果是 Native 方法,则其值为 `undefined`[^4]。 #### 2. JVM 策略 - **常见的 JVM 参数有哪些?它们的作用分别是什么?** 常见的 JVM 参数包括但不限于: - `-Xms` 和 `-Xmx`: 设置初始堆内存和最大堆内存。 - `-XX:NewRatio`: 新生代与老年代的比例。 - `-XX:+UseG1GC`: 使用 G1 垃圾回收器。 这些参数可以根据实际业务场景进行灵活整,以达到最性能表现[^1]。 - **如何判断是否存在内存泄漏?可以使用哪些工具分析?** 判断内存泄漏通常依赖于监控工具如 VisualVM 或 JConsole。这些工具能够帮助开发者查看内存占用情况、垃圾收集行为等指标。如果发现某些对象实例数量持续增长而未被释放,则可能存在内存泄漏问题。 #### 3. 编写高效的 Java 代码 - **为什么应避免在循环中创建大量临时对象?** 循环内部频繁创建新对象会导致大量的短生命周期对象进入垃圾回收队列,增加 GC 开销。因此建议重用已有对象或者采用 StringBuilder 来处理字符串拼接操作[^2]。 - **String 拼接的最佳实践是什么?** 对于少量固定次数的 String 拼接可以直接使用加号运算符 (`+`);而对于多次动态拼接则推荐使用 `StringBuilder` 类型代替简单相加方式,因为后者会产生多个中间产物并消耗更多资源。 #### 4. 并发编程中的性能考虑 - **锁化技术有哪些?举例说明其实现机制。** 锁化主要包括读写分离(ReadWriteLock)、分段锁(Segmented Locking)以及无锁算法(Lock-Free Algorithm)。例如 ConcurrentHashMap 就采用了分段锁的方式提高并发访问效率[^3]。 - **volatile 关键字有什么用途?它是否完全替代 synchronized 同步块?** Volatile 可确保变量修改后的可见性和防止指令重排序,但它无法解决原子性问题,所以在涉及复杂状态更新时仍需借助同步手段完成临界区保护。 ```java // 示例:Volatile 应用案例 public class Counter { private volatile int count = 0; public void increment() { count++; } public int getCount() { return count; } } ``` ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值