前言
我前几天出了一道面试题,问题是解决高并发限流的问题,一个面试者给我提到了计数器的功能,我觉得很有必要在这里说一下。
他给我说到他们公司用到的一个计数器,当请求量达到3000的化就不去处理。
高并发场景,就会遇到限流场景。先上他给我写的代码。
代码
定义一个计数器接口。
public interface Counter {
//加
int incr();
//减
int decr();
//获取当前值
int get();
}
定义接口实现类
public class CounterBasic implements Counter {
volatile int i=0;
@Override
public int incr() {
return i++;
}
@Override
public int decr() {
return i--;
}
@Override
public int get() {
return i;
}
}
我们测试以他写的这个计数器在高并发情况下能不能使用。
public class Test {
public static void main(String[] args) throws InterruptedException{
final Counter ct=new CounterBasic();
for(int i=0;i<2;i++){
new Thread(()->{
long begin=System.nanoTime();
for(int j=0;j<10000;j++){
ct.incr();
}
}).start();
}
Thread.sleep(6000L);
System.out.println("计数器的最终结果:"+ct.get());
}
}
我们定义了两个线程,每个线程跑10000次,我们希望看到的结果是20000,可打印出的结果小于20000。
问题解决
这个他哪里写错了,怎么去解决这个问题?
加锁
public class CounterBasic implements Counter {
volatile int i=0;
@Override
public synchronized int incr() {
return i++;
}
@Override
public synchronized int decr() {
return i--;
}
@Override
public int get() {
return i;
}
}
Aqs解决办法
public class CounterBasic implements Counter {
volatile int i=0;
Lock lock=new ReentrantLock();
@Override
public int incr() {
lock.lock();
try{
return i++;
} finally {
lock.unlock();
}
}
@Override
public int decr() {
lock.lock();
try{
return i--;
}finally {
lock.unlock();
}
}
@Override
public int get() {
return i;
}
}
原子整型
AtomicInteger i=new AtomicInteger (0);
底层原理
上面的基础面试我就把那个人淘汰了,知道为什么会出现这个问题么?
这样分析jvm执行过程,从源码到字节码去分析。Idea中引入jclasslib插件。我们打印出汇编指令。aload,dump,iconst_1,iadd。
Jmm数据原子操作
read(读取):从主内存读数据。
Load(载入):将主内存读取到的数据写入工作内存。
Use(使用):从工作内存读取数据来计算。
Assign(赋值):将计算好的值重新赋值到工作内存中。
Write(写入):将store过去的变量赋值给主内存的变量。
Lock(锁定):将主内存变量加锁,标识为线程独占状态。
Unlock(解锁):将主内存变量解锁,解锁后其它线程可以锁定该变量。
线程1回写主内存在store的时候,通过监听嗅探机制其它线程失效掉,如果第二个线程执行变量加一,没有store回主线程,就嗅探机制了,那它的加一操作就丢失掉了。
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CAS 机制
即compare and swap,属于硬件同步原语,处理器提供了基本内存操作的原子性保证。底层cas需要输入两个参数:旧值A和新值B,在操作时候先比较输入的救值A和内存中是否一致,不一致代表内存数据发生了变化,cas失败。
如果输入的旧值A和内存的数据一致,则将值A替换成值B,cas操作成功。
如果觉得《一道面试题验出JAVA多线程功底 深入浅出多线程 进来看看》对你有帮助,请点赞、收藏,并留下你的观点哦!