栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java面试突击3(1):Java基础面试

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java面试突击3(1):Java基础面试

目录

1、HashMap如何扩容?

2、高频并发问题

3、synchronized关键字的底层原理是什么?

4、你对CAS的理解以及其底层实现原理可以吗?

4.1、synchronized

4.2、CAS

5、ConcurrentHashMap实现线程安全的底层原理到底是什么?

 实现1

 实现2

总结:

6、你对JDK中的AQS理解吗?AQS的实现原理是什么?

公平锁和非公平锁

总结

7、线程池

7.1、说说线程池的底层⼯作原理可以吗?

7.2、线程池的核⼼配置参数都是⼲什么的?平时我们应该怎么⽤?

7.3、如果在线程池中使⽤⽆界阻塞队列会发⽣什么问题?

7.4、如果线程池的队列满了之后,会发⽣什么事情吗?

7.5、如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?

总结

8、谈谈你对Java内存模型的理解可以吗?

9、你知道Java内存模型中的原⼦性、有序性、可⻅性是什么?

9.1、可⻅性

9.2、原⼦性

9.3、有序性

10、能从Java底层⾓度聊聊volatile关键字的原理吗?

 11、你知道指令重排以及happens-before原则是什么吗?

12、volatile底层是如何基于内存屏障保证可⻅性和有序性的?


1、HashMap如何扩容?

底层是⼀个数组,当这个数组满了之后,他就会⾃动进⾏扩容,变成⼀个更⼤的数组,让你在⾥⾯可以去放更多的元素

2 倍扩容 [16 位的数组, <> -> <> -> <>] [32 位的数组, <> -> <>, <>] 数组长度 =16 n - 1      0000 0000 0000 0000 0000 0000 0000 1111 hash1     1111 1111 1111 1111 0000 1111 0000 0101 & 结果     0000 0000 0000 0000 0000 0000 0000 0101    = 5 ( index = 5 的位置) n - 1      0000 0000 0000 0000 0000 0000 0000 1111 hash2     1111 1111 1111 1111 0000 1111 0001 0101 & 结果     0000 0000 0000 0000 0000 0000 000 0 0101 = 5 ( index = 5 的位置) 在数组长度为 16 的时候,他们两个 hash 值的位置是⼀样的,⽤链表来处理,出现⼀个 hash 冲突的问题 如果数组的长度扩容之后 = 32 ,重新对每个 hash 值进⾏寻址,也就是⽤每个 hash 值跟新数组的 length - 1 进⾏与操作 n-1        0000 0000 0000 0000 0000 0000 000 1 1111 hash1     1111 1111 1111 1111 0000 1111 0000 0101 & 结果     0000 0000 0000 0000 0000 0000 0000 0101 = 5 ( index = 5 的位置) n-1        0000 0000 0000 0000 0000 0000 000 1 1111 hash2     1111 1111 1111 1111 0000 1111 0001 0101 & 结果     0000 0000 0000 0000 0000 0000 000 1 0101 = 21 ( index = 21 的位置)         判断⼆进制结果中是否多出⼀个bit的1,如果没多,那么就是原来的index,如果多了出来,那么就是index + oldCap,通过这个⽅式, 就避免了rehash 的时候,⽤每个 hash 对新数组 .length 取模,取模性能不⾼,位运算的性能⽐较⾼。

2、高频并发问题

        synchronized实现原理、CAS⽆锁化的原理、AQS是什么、Lock锁、ConcurrentHashMap的分段加锁的原理、线程池的原理、java内存模型、volatile说⼀下吗、对java并发包有什么了解?⼀连串的问题 。

3、synchronized关键字的底层原理是什么?

其实synchronized底层的原理,是跟jvm指令和monitor有关系的

你如果⽤到了 synchronized 关键字,在底层编译后的 jvm 指令中,会有 monitorenter 和 monitorexit 两个指令
monitorenter // 代码对应的指令 monitorexit
那么 monitorenter 指令执⾏的时候会⼲什么呢?          每个对象都有⼀个关联的monitor,⽐如⼀个对象实例就有⼀个monitor,⼀个类的Class对象也有⼀个monitor,如果要对这个对象加锁,那么必须获取这个对象关联的monitor的lock锁。 原理: monitor⾥⾯有⼀个计数器,从0开始的。如果⼀个线程要获取monitor的锁,就看看他的计数器是不 是0,如果是0的话,那么说明没⼈获取锁,他就可以获取锁了,然后对计数器加1。

这个monitor的锁是⽀持重⼊加锁的,什么意思呢,好⽐下⾯的代码⽚段

// 线程1
synchronized(myObject) {  -> 类的class对象来⾛的
// ⼀⼤堆的代码
synchronized(myObject) {
// ⼀⼤堆的代码
}
}

加锁,⼀般来说都是必须对⼀个对象进⾏加锁。

        如果⼀个线程第⼀次synchronized 那⾥,获取到了 myObject 对象的 monitor 的锁,计数器加 1 ,然后第⼆次 synchronized 那⾥,会再次获取myObject 对象的 monitor 的锁,这个就是重⼊加锁了,然后计数器会再次加 1 ,变成2。         这个时候,其他的线程在第⼀次synchronized那⾥,会发现说 myObject 对象的 monitor 锁的计数器是⼤于 0 的,意味着被别⼈加锁了,然后此时线程就会进⼊block 阻塞状态,什么都⼲不了,就是等着获取锁 。         接着如果出了synchronized修饰的代码⽚段的范围,就会有⼀个 monitorexit 的指令,在底层。此时获取锁的线程就会对那个对象的monitor的计数器减 1 ,如果有多次重⼊加锁就会对应多次减 1 ,直到最后,计数器是 0 。         然后后⾯block住阻塞的线程,会再次尝试获取锁,但是只有⼀个线程可以获取到锁 。

4、你对CAS的理解以及其底层实现原理可以吗?

        多个线程他们可能要访问同⼀个数据  HashMap map = new HashMap();

此时有多个线程要同时读写类似上⾯的这种内存⾥的数据,此时必然出现多线程的并发安全问题。

4.1、synchronized
        synchronized(map) {             // 对 map ⾥的数据进⾏复杂的读写处理

        }

        此时,synchronized 意思就是针对当前执⾏这个⽅法的 myObject 对象进⾏加锁,只有⼀个线程可以成功 的对myObject加锁,可以对他关联的monitor的计数器去加1,加锁,⼀旦多个线程并发的去进⾏synchronized加锁,串⾏化,效率并不是太⾼ ,很多线程,都需要排队去执⾏。

4.2、CAS

        CAS在底层的硬件级别给你保证⼀定是原⼦的,同⼀时间只有⼀个线程可以执⾏ CAS ,先⽐较再设置,其他的线程的 CAS 同时间去执⾏此时会失败。

线程1和线程分别读取0到本地内存。线程1货渠到0后+1,然后比较本地值0和内存值0相等,则更新内存值为1。线程2比较本地值0和内存值1不等则更新失败。则尝试设置为2成功。

5、ConcurrentHashMap实现线程安全的底层原理到底是什么?

        多个线程要访问同⼀个数据,synchronized加锁,CAS去进⾏安全的累加,去实现多线程场景下的安全的更新⼀个数据的效果,⽐较多的⼀个场景下,可能就是多个线程同时读写⼀个HashMap。使用synchronized,也没这个必要 。

 实现1 HashMap的⼀个底层的原理,本⾝是⼀个⼤的⼀个数组,[有很多的元素]
        Map map = new Map();
        多个线程过来,线程1 要 put 的位置是数组 [5] ,线程 2 要 put 的位置是数组 [21]
        map.put(xxxxx,xxx);
        明显不好,数组⾥有很多的元素,插入不同位置时可以并发插入的。所以除⾮是对同⼀个元素执⾏put 操作,此时呢需要多线程是需要进⾏同步的。

 实现2         JDK并发包⾥推出了⼀个ConcurrentHashMap ,他默认实现了线程安全性。         在JDK 1.7及之前的版本⾥, 分段 即将原数组拆分为多个数组,每个数组分别加锁:  [数组 1] , [ 数组 2] , [ 数组 3] -> 每个数组都对应⼀个锁分段加锁,多个线程过来, 线程1要put的位置是数组1[5],线程2要put的位置是数组2[21]。此时对于不同线程在不同数组中插入数据没有冲突。         JDK 1.8以及之后,做了⼀些优化和改进,锁粒度的细化 。[⼀个⼤的数组 ] ,数组⾥每个元素进⾏ put 操作, 都是有⼀个不同的锁,刚开始进⾏put的时候,如果两个线程都是在数组[5]这个位置进⾏put,这个时候,对数组[5]这个位置进⾏put的时候,采取的是CAS的策略 。          同⼀个时间,只有⼀个线程能成功执⾏这个CAS ,就是说他刚开始先获取⼀下数组 [5] 这个位置的值, null ,然后执⾏ CAS ,线程 1 ⽐较⼀下put 进去我的这条数据,同时间其他的线程执⾏ CAS ,都会失败。          分段加锁,通过对数组每个元素执⾏CAS的策略 ,如果是很多线程对数组⾥不同的元素执⾏ put ,⼤家是没有关系的,如果其他⼈失败了,其他⼈此时会发现说,数组[5] 这位置,已经给刚才⼜⼈放进去值了。就 需要在这个位置基于链表+红⿊树来进⾏处理,synchronized(数组[5]),加锁,基于链表或者是红⿊树在这个位置 插进去⾃⼰的数据。          如果你是对数组⾥同⼀个位置的元素进⾏操作,才会加锁串⾏化处理;如果是对数组不同位置的元素操作,此时⼤家可以并发执⾏的。

总结: 首先对整个数组加锁  ------》  对数组分段加锁  ------》   对每个元素加锁。

6、你对JDK中的AQS理解吗?AQS的实现原理是什么?

         多线程同时访问⼀个共享数据,可以使用sychronized,CAS,ConcurrentHashMap(并发安全的数据结构可以来⽤),Lock等方式处理。         synchronized就有点不⼀样了,底层基于  AQS , Abstract Queue Synchronizer ,抽象队列同步器 。以及Semaphore、其他⼀些的并发包下的都是基于AQS。
ReentrantLock lock = new ReentrantLock(true);  => ⾮公平锁 // 多个线程过来,都尝试 lock.lock(); lock.unlock();

原理分析:AQS内部的重要变量state=0。此时有多个线程都执行lock.lock()进行加锁。

  •  首先线程1和线程2通过CAS去更新state的值为1, 同一时间线程1执行成功(线程1获取state0值,比较0与0相等则更新AQS中的state为1)。此时线程1加锁成功线程2加锁失败。
  •  然后AQS内部有一个加锁线程记录加锁成功的线程(加锁线程=线程1)。
  •  然后AQS内部有一个等待队列记录加锁失败的线程,故线程2进入等待队列。
  • 此时线程1执行成功后释放锁,将state=0,加锁线程=null,唤醒等待队列队头的线程即线程2,线程2开始CAS更新state=1和加锁线程=线程2执行。。。

公平锁和非公平锁

非公平锁:当上图线程2被线程1唤醒后,此时线程3直接先进行CAS将state=1和加锁线程=线程3。然后线程2进行CAS失败继续进入等待队列。故此时线程2时不公平的。

公平锁:当上图线程2被线程1唤醒后,此时线程3首先会判断队列:发现队列有线程2,线程3直接进入等待队列排队。

总结 state变量 -> CAS -> 失败后进⼊队列等待 -> 释放锁后唤醒   公平与非公平

7、线程池

7.1、说说线程池的底层⼯作原理可以吗?          系统是不可能说让他⽆限制的创建很多很多的线程的,会构建⼀个线程池,有⼀定数量的线程,让他们执⾏各种各样的任务 ,线程执⾏完任务之后,不要销毁掉⾃⼰,继续去等待执⾏下⼀个任务。
ExecutorService threadPool = Executors.newFixedThreadPool(3) -> 3: corePoolSize threadPool.submit(new Callable() {        public void run() {} }) ;
  •  提交任务,先看⼀下线程池⾥的线程数量是否⼩于corePoolSize,也就是3,如果⼩于,直接创建⼀个线程出来执⾏你的任务。
  • 如果执⾏完你的任务之后,这个线程是不会死掉,他会尝试从⼀个⽆界linkedBlockingQueue⾥获取新的任务,如果没有新的任务,此时就会阻塞住,等待新的任务到来。
  • 你持续提交任务,上述流程反复执⾏,只要线程池的线程数量⼩于corePoolSize,都会直接创建新线程来执⾏这个任务,执⾏完了就尝试从⽆界队列⾥获取任务,直到线程池⾥有corePoolSize个线程。
  •  接着再次提交任务,会发现线程数量已经跟corePoolSize⼀样⼤了,此时就直接把任务放⼊队列中就可以了,线程会争抢获取任务执⾏的,如果所有的线程此时都在执⾏任务,那么⽆界队列⾥的任务就可能会越来越多。
  • newFixedThreadPool的队列是linkedBlockingQueue,一个⽆界阻塞队列。

7.2、线程池的核⼼配置参数都是⼲什么的?平时我们应该怎么⽤?

        代表线程池的类是ThreadPoolExecutor。

        创建⼀个线程池参数corePoolSize , maximumPoolSize , keepAliveTime queue ,如果你不⽤ fixed 之类的线程池,⾃⼰完全可以通过这个构造函数就创建⾃⼰的线程池。
corePoolSize : 3 maximumPoolSize : Integer.MAX_VALUE keepAliveTime : 60s new ArrayBlockingQueue(200)
        如果说你把queue 做成有界队列,⽐如说 new ArrayBlockingQueue(200) ,那么假设 corePoolSize 个线程都在繁忙的⼯作,⼤量任务进⼊有界队列,队列满了,此时怎么办?         这个时候假设你的maximumPoolSize 是⽐ corePoolSize ⼤的,此时会继续创建额外的线程放 ⼊线程池⾥,来处理这些任务,然后超过corePoolSize数量的线程如果处理完了⼀个任务也会尝试 从队列⾥去获取任务来执⾏。         如果额外线程都创建完了去处理任务,队列还是满的,此时还有新的任务来怎么办?         只能reject 掉,他有⼏种 reject 策略,可以传⼊ RejectedExecutionHandler。
(1)AbortPolicy   (2)DiscardPolicy   (3)DiscardOldestPolicy   (4)CallerRunsPolicy   (5) ⾃定义
        如果后续慢慢的队列⾥没任务了,线程空闲了,超过corePoolSize 的线程会⾃动释放掉,在 keepAliveTime 之后就会释放。

         根据上述原理去定制⾃⼰的线程池,考虑到corePoolSize的数量,队列类型,最⼤线程数量,拒绝策略,线程释放时间

⼀般⽐较常⽤的是:fixed线程。

7.3、如果在线程池中使⽤⽆界阻塞队列会发⽣什么问题? ⾯试题: 在远程服务异常的情况下,使⽤⽆界阻塞队列,是否会导致内存异常飙升?         当线程调用远程服务调⽤超时,导致任务处理很慢,而任务进来会很快导致  。 队列变得越来越⼤,此时会导致内存飙升起来,⽽且还可能会导致你会 OOM ,内存溢出。

7.4、如果线程池的队列满了之后,会发⽣什么事情吗? 有界队列,可以避免内存溢出。
corePoolSize: 10 maximumPoolSize : 200 ArrayBlockingQueue(200)
        ⾃定义⼀个reject 策略,如果线程池⽆法执⾏更多的任务了,此时 建议你可以把这个任务信息持久化写⼊磁盘⾥去,后台专门启动⼀个线程,后续等待你的线程池的⼯作负载降低了,他可以慢慢的从磁盘⾥读取之前持久化的任务,重新提交到线程池⾥去执⾏ 。                  你可以 ⽆限制的不停的创建额外的线程出来,⼀台机器上,有⼏千个线程,甚⾄是⼏万 个线程, 每个线程都有⾃⼰的栈内存,占⽤⼀定的内存资源,会导致内存资源耗尽,系统也会崩溃掉 即使内存没有崩溃,会导致你的机器的cpu load,负载,特别的 ⾼。

7.5、如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?         必然会导致线程池⾥的积压的任务实际上来说都是会丢失的。 如果说你要提交⼀个任务到线程池⾥去,在提交之前,⿇烦你先在数据库⾥插⼊这个任务的信息,更新他的状态:未提交、已提交、已完成。提交成功之后,更新他的状态是已提交状态 。          系统重启,后台线程去扫描数据库⾥的未提交和已提交状态的任务,可以把任务的信息读取出来,重新提交到线程池⾥去,继续进⾏执⾏ 。 总结 无界队列不断产生任务:导致内存飙升,内存溢出,OOM 有界队列不断创建线程:线程栈导致内存资源耗尽,cpu负载高。

8、谈谈你对Java内存模型的理解可以吗?

read、load、use  /  assign、store、write 

9、你知道Java内存模型中的原⼦性、有序性、可⻅性是什么?

连环炮:Java内存模型 -> 原⼦性、可⻅性、有序性 -> volatile -> happens-before / 内存屏障

也就是并发编程过程中,可能会产⽣的三类问题

9.1、可⻅性 之前⼀直给⼤家代码演示,画图演示,其实说的就是并发编程中可⻅性问题 没有可⻅性: 线程1 将自己内存的值read、load、use、assing、store、write后变成1,而线程2在主内存值变化后没有感知到,仍然使用data=0故一直while休眠。 可见性: 主内存更新后的值被其他线程立马看到。

 

9.2、原⼦性 有原⼦性,没有原⼦性 原⼦性: data++ ,必须是独⽴执⾏的,没有⼈影响我的,⼀定是我⾃⼰执⾏成功之后,别⼈才能来进⾏下⼀次 data++ 的执⾏。

9.3、有序性

        对于代码,同时还有⼀个问题是指令重排序,编译器和指令器,有的时候为了提⾼代码执⾏效率,会将指令重排序,就是说⽐如下⾯的代码。

具备有序性:不会发⽣指令重排导致我们的代码异常; 不具备有序性:可能会发⽣⼀些指令重排,导致代码可能会出现⼀些问题。

        重排序之后,让flag = true 先执⾏了,会导致线程 2 直接跳过 while 等待,执⾏某段代码,结果 prepare() ⽅法还没执⾏,资源还没准备好呢,此时就会导致代码逻辑出现异常。

10、能从Java底层⾓度聊聊volatile关键字的原理吗?

内存模型 -> 原⼦性、可见性、有序性 -> volatile

        讲清楚volatile 关键字,直接问你 volatile 关键字的理解,对前⾯的⼀些问题,这个时候你就应该⾃⼰去主动从内存模型开始讲起,原⼦性、可见性、有序性的理解,volatile 关键字的原理。         volatile关键字是⽤来解决可见性和有序性,在有些罕见的条件之下,可以有限的保证原⼦性,他主要不是⽤来保证原⼦性的。

   volatile修饰的变化,会保证在其他线程工作内存中的该变量值失效。故线程2会强制从主内存读data数据:read/load/use。

      在很多的开源中间件系统的源码⾥,⼤量的使⽤了volatile,每⼀个开源中间件系统,或者是⼤数据系统,都多线程并发,volatile

当脚本修改running为false后,添加了volatile能保证主线程

 11、你知道指令重排以及happens-before原则是什么吗?

        volatile关键字和有序性的关系,volatlie是如何保证有序性的,如何避免发⽣指令重排的。

下面代码如果没有有序性,可能prepare()和flag=true顺序更改,会导致线程2在没有等线程1准备好资源的情况下进行操作。

        java中有⼀个 happens-before 原则:         编译器、指令器可能对代码重排序,乱排,要守⼀定的规则,happens-before 原则,只要符合 happens-before 的原则,那么就不能胡乱重排,如果不符合这些规则的话,那就可以⾃⼰排序。
  1. 程序次序规则:⼀个线程内,按照代码顺序,书写在前⾯的操作先⾏发⽣于书写在后⾯操作
  2. 锁定规则:⼀个unLock操作先⾏发⽣于后⾯对同⼀个锁的lock操作,⽐如说在代码⾥有先对⼀个lock.lock(),lock.unlock(),lock.lock()。
  3. volatile变量规则:对⼀个volatile变量的写操作先⾏发⽣于后⾯对这个volatile变量的读操作,volatile变量写,再是读,必须保证是 先写,再读。
  4. 传递规则:如果操作A先⾏发⽣于操作B,⽽操作B⼜先⾏发⽣于操作C,则可以得出操作A先⾏发⽣于操作C
  5. 线程启动规则:Thread对象的start()⽅法先⾏发⽣于此线程的每个⼀个动作,thread.start()在thread.interrupt()前。
  6. 线程中断规则:对线程interrupt()⽅法的调⽤先⾏发⽣于被中断线程的代码检测到中断事件的发⽣
  7. 线程终结规则:线程中所有的操作都先⾏发⽣于线程的终⽌检测,我们可以通过Thread.join()⽅法结束、Thread.isAlive()的返回值,⼿段检测到线程已经终⽌执⾏
  8. 对象终结规则:⼀个对象的初始化完成先⾏发⽣于他的finalize()⽅法的开始
        上⾯这8 条原则的意思很显⽽易⻅,就是程序中的代码如果满⾜这个条件,就⼀定会按照这个规则来保证指令的顺序。          面试回答:规则制定了在⼀些特殊情况下,不允许编译器、指令器对你写的代码进⾏指令重排,必须保证你的代码的有序性 。但是如果没满⾜上⾯的规则,那么就可能会出现指令重排。         8 条规则之外,可以随意重排指令。

        ⽐如这个例⼦,如果⽤volatile来修饰flag变量,⼀定可以让prepare()指令在flag = true之前先执⾏,这就禁⽌了指令重排。因为volatile要求的是,volatile前⾯的代码⼀定不能指令重排到volatile变量操作后⾯,volatile后⾯的代码也不能指令重排到volatile前⾯。

        指令重排 -> happens-before -> volatile 起到避免指令重排

12、volatile底层是如何基于内存屏障保证可⻅性和有序性的?

连环炮:内存模型 -> 原⼦性、可⻅性、有序性 - > volatile+可⻅性 -> volatile+有序性(指令重排 + happens-before) -> voaltile+原⼦性 -> volatile底层的原理(内存屏障级别的原理)

1、volatile + 原⼦性: 不能够保证原⼦性,虽然说有些极端特殊的情况下有保证原⼦性的效果,杠精拿着⼀些极端场景下例⼦说volatile也可以原⼦性, oracle , 64 位的 long 的数字进⾏操作。 保证原⼦性: synchronized,lock,加锁 。 2、volatile底层原理,如何实现保证可⻅性的呢?如何实现保证有序性的呢? (1)lock指令:volatile保证可⻅性         对volatile 修饰的变量,执⾏ 写操作的话,JVM会发送⼀条lock前缀指令给CPU , CPU 在计算完之后会⽴即将这个值写回主内存,同时因为有 MESI缓存⼀致性协议 ,所以 各个CPU都会对总线进⾏嗅探,⾃⼰本地缓存中的数据是否被别⼈修改 。如果 发现别⼈修改了某个缓存的数据,那么CPU就会将⾃⼰本地缓存的数据过期掉,然后这个CPU上执⾏的线程在读取那个变量的时候,就会从主内存重新加载最新的数据 了。          lock前缀指令 + MESI缓存⼀致性协议 。 (2)内存屏障:volatile禁⽌指令重排序         volatille是如何保证有序性的?         加了volatile 的变量,可以保证前后的⼀些代码不会被指令重排,这个是如何做到的呢?指令重排是怎么回事,volatile 就不会指令重排。
Load1 : int localVar = this.variable Load2 : int localVar = this.variable2 LoadLoad 屏障: Load1 ; LoadLoad ; Load2 ,确保 Load1 数据的装载先于 Load2 后所有装载指令,他的意思, Load1 对应的代码和 Load2 对应的代码,是不能指令重排的 Store1 this.variable = 1 StoreStore 屏障 Store2 : this.variable2 = 2 StoreStore 屏障: Store1 ; StoreStore ; Store2 ,确保 Store1 的数据⼀定刷回主存,对其他 cpu 可⻅,先于 Store2 以及后续指令 LoadStore 屏障: Load1 ; LoadStore ; Store2 ,确保 Load1 指令的数据装载,先于 Store2 以及后续指令 StoreLoad 屏障: Store1 ; StoreLoad ; Load2 ,确保 Store1 指令的数据⼀定刷回主存,对其他 cpu 可⻅,先于 Load2 以及后续指令的数 据装载 volatile 的作⽤是什么呢? volatile variable = 1 this.variable = 2 => store 操作 int localVariable = this.variable => load 操作
        对于volatile 修改变量的读写操作,都会加⼊内存屏障 。每个volatile 写操作前⾯,加 StoreStore 屏障,禁⽌上⾯的普通写和他重排;每个 volatile 写操作后⾯,加 StoreLoad 屏障,禁⽌跟下⾯的。         volatile读/ 写重排 :每个volatile 读操作后⾯,加 LoadLoad 屏障,禁⽌下⾯的普通读和 voaltile 读重排;每个 volatile 读操作后⾯,加 LoadStore 屏障,禁⽌下⾯的普通写和volatile 读重排。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/329032.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号