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

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值