BAT面试总结


本章为听课笔记,主要课程为尚硅谷周阳老师的相关课程,里面的一些图为视频截图

volatile关键字相关,JMMJava内存模型

volatile关键字相关,JMM Java内存模型

并发和并行的区别

1、并发:多个线程访问同一资源(如秒杀系统)
2、并行:一边做XX,一边做XX同时进行(如一边看小说,一边听音乐)

volatile关键字

volatile是Java虚拟机提供的轻量级同步机制:(轻量级可以理解为乞丐版)
1、保证可见性
2、不保证原子性
3、禁止指令重排

1、保证可见性

这里涉及到JMM内存模型,这是一个抽象的不是真实存在的一组规则或规范(龙不存在,但我们都懂),在计算机当中,每个对象申请的变量存在于主内存当中(这里不一定对),而每个线程都有自己的工作内存,如果线程想对变量进行读写操作,不是直接对主内存中的变量操作,而是拷贝一份共享变量存在于自己的工作内存当中,线程A、B、C都拷贝了一份变量存在于自己的工作内存当中,当线程A操作了自己的工作内存变量并同步到主内存时,如果线程B、C没有得到通知,他们持有的仍是旧的变量,volatile关键字的可见性就是在更新主内存变量时,通知其他线程。
如果去掉volatile关键字,main线程将一直等待下去,可知,不加volatile,main线程无法“可见”主内存的改变。

import java.util.concurrent.TimeUnit;

public class volatile可见性 {
    public static void main(String[] args) {
        myData myData = new myData();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t data="+myData.data);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.add();
            System.out.println(Thread.currentThread().getName()+"\t data="+myData.data);
        },"AAA").start();

        while (myData.data==0){ }
        System.out.println(Thread.currentThread().getName()+"\t data="+myData.data);
    }

}
class myData{
    //去掉volatile关键字后,主线程一直等待
    volatile int data=0;

    public void add(){
        data=60;
    }
}

不加volatile结果
加上volatile的结果

2、不保证原子性

从代码示例中我们可以看出,一共二十个线程每个线程都执行i++的操作1000次,最后结果并不是两万。主要原因是线程的执行速度太快,会发生不同的线程,来不及通知其他线程,就被其他线程覆盖写入主内存。从结果图中我们可以看到。volatile执行结果一定是小于两万的(有极小概率等于20000),正是这个原因。
解决办法也已经给出:
1、加synchronized关键字这个太重量级
2、使用JUC atomic包下的AtomicInteger具体使用已经写出,这里还涉及到JUC下的getAndIncrement()方法,类似于i++,具体情况等看到再深入分析。

public class volatile情况 {
    public static void main(String[] args) {
        myData myData = new myData();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }
//                System.out.println(Thread.currentThread().getName()+"\t data="+myData.data);
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount()>2){//后台进程大于2(一个main,一个GC)
            Thread.yield();//让出当前线程
        }
        System.out.println(Thread.currentThread().getName()+"\t data="+myData.data);
    }

class myData{
    //去掉volatile关键字后,主线程一直等待
    volatile int data=0;

    public void add(){
        data=60;
    }
    /**
     * 解决办法
     * 1、加synchronized
     * 2、使用java.util.concurrent.atomic中的AtomicInteger 保证原子性
     */
    
    public void addPlusPlus(){
        data++;
    }
    
    AtomicInteger atomicInteger=new AtomicInteger();
    public void atomicAddPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}

执行结果对比

3、禁止指令重排

指令重排是计算机在编译过程对代码执行顺序的优化,但多线程操作时可能会导致数据不一致的结果。
指令重排

 public void example(){
     /**
      * 执行结果可能为 1234
      * 1324
      * 2134
      * 2143
      */
     int x=1;//1
     int y=1;//2
     x=x+1;//3
     y=x*x;//4
 }

多线程这里不一致性参杂在一起,比较难理解
多线程情况
应该是这种情况,但这种情况夹杂着多线程执行速度的问题,而且打印也会有快慢的问题,我暂时还没想明白,先放一放。

  public class 指令重排1 {
   int a=0,b=0,x=0,y=0;
  public static void main(String[] args) {
      指令重排1 demo = new 指令重排1();

      for (int i = 0; i < 2; i++) {
          int finalI = i;
          new Thread(()->{
              if (finalI % 2 == 0) {
                  demo.example1();
              } else {
                  demo.example2();
              }
          },String.valueOf(i)).start();
      }

  }

  public void example1(){
      x=a;
      b=1;
  }
  public void example2(){
      y=b;
      a=2;
  }
}

第三个案例,也未实现,不知道是线程调度出现了问题还是概率太低,理论上可能a可能等于5;

public class 指令重排1 {
   int a=0;
   boolean flag=false;
   public static void main(String[] args) {
       指令重排1 demo = new 指令重排1();

       new Thread(()->{
           demo.example1();
       },"A").start();
       new Thread(()->{
           demo.example2();
       },"B").start();


   }

   public void example1(){
       a=1;
       flag=true;
   }
   public void example2(){
       if (flag){
           a=a+5;
           System.out.println("result="+a);
       }
   }
}

上面的指令重排可在加入volatile后禁重排,主要原理是底层的理解 Memory barrier(内存屏障)
在这里插入图片描述在这里插入图片描述
关于volatile的应用再写一章吧,涉及到单例,DCL相关知识

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值