Java最新【并发编程】java并发编程:CAS(Compare and Swap)(1),京东高级java面试

最后

最后,强调几点:

  • 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
  • 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
  • 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。

面试答案

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

for (int i = 0; i< 100000; ++i){

count+=1;

}

}

运行结果

第一次:

第二次:

第三次:

这里就有意思了,加了锁的运行时间居然比不加锁的运行时间还少?是不是觉得有点不可思议了?其实这个也不难理解,这里就要牵扯到cpu缓存以及缓存与内存的回写机制了,感兴趣的小伙伴可以自行百度,今天的重点不在这里。

第二种:三个线程使用同一把锁

改造代码,去掉add方法的synchronized关键字,将synchronized写在add方法内,新建一把钥匙(成员变量:lock),让三个累加都累加操作使用这把钥匙,代码如下:

package com.ymy.test;

public class CASTest {

private static long count = 0;

private static final String lock = “lock”;

/**

  • 累加10w

*/

private static void add() {

synchronized(lock){

for (int i = 0; i < 100000; ++i) {

count += 1;

}

}

}

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

//开启三个线程 t1 t2 t3

Thread t1 = new Thread(() -> {

add();

});

Thread t2 = new Thread(() -> {

add();

});

Thread t3 = new Thread(() -> {

add();

});

long starTime = System.currentTimeMillis();

//启动三个线程

t1.start();

t2.start();

t3.start();

//让线程同步

t1.join();

t2.join();

t3.join();

long endTime = System.currentTimeMillis();

System.out.println(“累加完成,count:” + count);

System.out.println(“耗时:” + (endTime - starTime) + " ms");

}

}

结果如下:

这两种加锁方式都能保证线程的安全,但是这里你需要注意一点,如果是在方法上加synchronized而不加static关键字的话,必须要保证多个线程共用这一个对象,否者加锁无效。

原子类

原子类工具有很多,我们举例的累加操作只用到其中的一种,我们一起看看java提供的原子工具有哪些:

工具类还是很丰富的,我们结合需求来讲解一下其中的一种,我们使用:AtomicLong。

AtomicLong提供了两个构造函数:

value:原子操作的初始值,调用无参构造value=0;调用有参构造value=指定值

其中value还是被volatile 关键字修饰,volatile可以保证变量的可见性,什么叫可见性?可见性有一条很重要的规则:Happens-Before 规则,意思:前面一个操作的结果对后续操作是可见的,线程1对变量A的修改其他线程立马可以看到,具体请自行百度。

我们接着来看累加的需求,AtomicLong提供了一个incrementAndGet(),源码如下:

/**

  • Atomically increments by one the current value.

  • @return the updated value

*/

public final long incrementAndGet() {

return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;

}

Atomically increments by one the current value :原子的增加一个当前值。好了,我们现在试着将互斥锁修改成原子类工具,改造代码:

**1.实例化一个Long类型的原子类工具;

2.再for循环中使用incrementAndGet()方法进行累加操作。**

改造后的代码:

package com.ymy.test;

import java.util.concurrent.atomic.AtomicLong;

public class CASTest {

// private static long count = 0;

//

// private static final String lock = “lock”;

private static AtomicLong atomicLong = new AtomicLong();

/**

  • 累加10w

*/

private static void add() {

for (int i = 0; i < 100000; ++i) {

atomicLong.incrementAndGet();

}

}

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

//开启三个线程 t1 t2 t3

Thread t1 = new Thread(() -> {

add();

});

Thread t2 = new Thread(() -> {

add();

});

Thread t3 = new Thread(() -> {

add();

});

long starTime = System.currentTimeMillis();

//启动三个线程

t1.start();

t2.start();

t3.start();

//让线程同步

t1.join();

t2.join();

t3.join();

long endTime = System.currentTimeMillis();

//System.out.println(“累加完成,count:” + count);

System.out.println(“累加完成,count:” + atomicLong);

System.out.println(“耗时:” + (endTime - starTime) + " ms");

}

}

结果:

可以得到累加的结果也是:30w,但时间却比互斥锁要久,这是为什么呢?我们一起来解剖一下源码。

AtomicLong  incrementAndGet()源码解析

/**

  • Atomically increments by one the current value.

  • @return the updated value

*/

public final long incrementAndGet() {

return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;

}

public final long getAndAddLong(Object var1, long var2, long var4) {

long var6;

do {

var6 = this.getLongVolatile(var1, var2);

} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

return var6;

}

我们来看看getAndAddLong方法,发现内部使用了一个 do  while  循环,我们看看循环的条件是什么

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这是循环条件的源码,不知道你们发现没有一个关键字:native,表示java代码已经走完了,这里需要调用C/C++代码,这里调用的时C++代码,在这里就要解释一下为什么原子类的比较和赋值是线程安全的,那是因为c++代码中是有加锁的,不知道你们是否了解过内存与cpu的消息总线制,c++就是再消息总线中加了lock,保证了互斥性,所以对比和赋值是一个原子操作,线程安全的。

Unsafe,这个类可以为原子工具类提供硬件级别的原子性,虽然我们java中使用的这些原子工具类虽然都是无锁的,但是我们无需考虑他的多线程安全问题。

为什么原子类比互斥锁的效率低?


好了,现在来思考一下为什么原子工具类的效率会比互斥锁低?明明没有加锁,反而比加了锁慢,这是不是有点不合常理?其实这很符合常理,我们一起来分析一波,CAS(Compare and Swap)重在比较,我们看源码的时候发现有一个  do  while循环,这个循环的作用是什么呢?

1.判断期望值是否和内存中的值一致;

2.如果不一致,获取内存中最新的值(var6),此时期望值就等于了var6,使用改期望值继续与内存中的值做对比,直到发现期望值和内存中的值一致,+1之后返回结果。

这里有一个问题不知道你们发现没有,就是这个循环问题,

1.我们假设线程1最先访问内存中的num值=0;加载到cpu中;

最后

笔者已经把面试题和答案整理成了面试专题文档

image

image

image

image

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

[外链图片转存中…(img-Lt4K7OX9-1715419481229)]

[外链图片转存中…(img-46TALCMI-1715419481230)]

[外链图片转存中…(img-NllVejR7-1715419481230)]

[外链图片转存中…(img-k5vGkgCG-1715419481230)]

[外链图片转存中…(img-XieMGpfw-1715419481230)]

[外链图片转存中…(img-tKxQAFcN-1715419481231)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值