2019年阿里面试题(不定时持续更新)

Java
1. 多个线程同时读写,读线程的数量远大于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?

a.数据库读写分离;

b.一种是代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized;

乐观锁,(不推荐悲观锁),服务层面可以采取负载均衡;负载均衡有几种实现方式(HTTP客户端 Feign.以及nginx),缓存

c.非线程安全类

public class Counter {

private int count;

public int getCount(){

      return count++;

  }

}

线程安全类

public class Counter {

    private int count;

    AtomicInteger atomicCount = new AtomicInteger( 0 );
  /**1、synchronized 所以线程安全*/
    public synchronized int getCount(){
        return count++;

    }
    /** 2、原子增长的操作,所以线程安全 */
    public int getCountAtomically(){
        return atomicCount.incrementAndGet();
    }
}

2. JAVA的AQS是否了了解,它是干嘛的?

提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一些问题:当我们通过实例化一个ReentrantLock并且调用它的lock或unlock的时候,这其中发生了什么?如果多个线程同时对同一个锁实例进行lock或unlcok操作,这其中又发生了什么?

**什么是可重入锁?**

ReentrantLock是可重入锁,什么是可重入锁呢?**可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待。**可重入锁是如何实现的呢?这要从ReentrantLock的一个内部类Sync的父类说起,Sync的父类是AbstractQueuedSynchronizer(后面简称AQS)。

**什么是AQS?**

AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可以这么说,JCU包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。**AQS的核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。**当state的值为0的时候,标识改Lock不被任何线程所占有。

**ReentrantLock锁的架构**

ReentrantLoc的架构相对简单,主要包括一个Sync的内部抽象类以及Sync抽象类的两个实现类。上面已经说过了Sync继承自AQS,他们的结构示意图如下:

上图除了AQS之外,我把AQS的父类AbstractOwnableSynchronizer(后面简称AOS)也画了进来,可以稍微提一下,AOS主要提供一个exclusiveOwnerThread属性,用于关联当前持有该锁的线程。另外、Sync的两个实现类分别是NonfairSync和FairSync,由名字大概可以猜到,一个是用于实现公平锁、一个是用于实现非公平锁。那么Sync为什么要被设计成内部类呢?我们可以看看AQS主要提供了哪些protect的方法用于修改state的状态,我们发现Sync被设计成为安全的外部不可访问的内部类。ReentrantLock中所有涉及对AQS的访问都要经过Sync,其实,Sync被设计成为内部类主要是为了安全性考虑,这也是作者在AQS的comments上强调的一点。

**AQS的等待队列**

作为AQS的核心实现的一部分,举个例子来描述一下这个队列长什么样子,我们假设目前有三个线程Thread1、Thread2、Thread3同时去竞争锁,如果结果是Thread1获取了锁,Thread2和Thread3进入了等待队列,那么他们的样子如下:

 

 

 

AQS的等待队列基于一个双向链表实现的,HEAD节点不关联线程,后面两个节点分别关联Thread2和Thread3,他们将会按照先后顺序被串联在这个队列上。这个时候如果后面再有线程进来的话将会被当做队列的TAIL。

**1)入队列**

我们来看看,当这三个线程同时去竞争锁的时候发生了什么?

代码:

public final void acquire(int arg) {

    if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

}

三个线程同时进来,他们会首先会通过CAS去修改state的状态,如果修改成功,那么竞争成功,因此这个时候三个线程只有一个CAS成功,其他两个线程失败,也就是tryAcquire返回false。接下来,addWaiter会把将当前线程关联的EXCLUSIVE类型的节点入队列

代码:

privateNode addWaiter(Node mode) {
    Node node = newNode(Thread.currentThread(), mode);
    Node pred = tail;
    if(pred != null) {
        node.prev = pred;
        if(compareAndSetTail(pred, node)) {
            pred.next = node;
            returnnode;
        }
    }
    enq(node);
    returnnode;
}

解读:

如果队尾节点不为null,则说明队列中已经有线程在等待了,那么直接入队尾。对于我们举的例子,这边的逻辑应该是走enq,也就是开始队尾是null,其实这个时候整个队列都是null的

代码:

privateNode enq(finalNode node) {
    for(;;) {
        Node t = tail;
        if(t == null) { // Must initialize
            if(compareAndSetHead(newNode()))
                tail = head;
        } else{
            node.prev = t;
            if(compareAndSetTail(t, node)) {
                t.next = node;
                returnt;
            }
        }
    }
}

解读:

如果Thread2和Thread3同时进入了enq,同时t==null,则进行CAS操作对队列进行初始化,这个时候只有一个线程能够成功,然后他们继续进入循环,第二次都进入了else代码块,这个时候又要进行CAS操作,将自己放在队尾,因此这个时候又是只有一个线程成功,我们假设是Thread2成功,哈哈,Thread2开心的返回了,Thread3失落的再进行下一次的循环,最终入队列成功,返回自己。

**2)并发问题**

基于上面两段代码,**他们是如何实现不进行加锁,当有多个线程,或者说很多很多的线程同时执行的时候,怎么能保证最终他们都能够乖乖的入队列而不会出现并发问题的呢?**这也是这部分代码的经典之处,**多线程竞争,热点、单点在队列尾部,多个线程都通过【CAS+死循环】这个free-lock黄金搭档来对队列进行修改,每次能够保证只有一个成功,如果失败下次重试,如果是N个线程,那么每个线程最多loop N次,最终都能够成功。**

**3)挂起等待线程**

上面只是addWaiter的实现部分,那么节点入队列之后会继续发生什么呢?那就要看看acquireQueued是怎么实现的了,为保证文章整洁,代码我就不贴了,同志们自行查阅,我们还是以上面的例子来看看,Thread2和Thread3已经被放入队列了,进入acquireQueued之后:

1.  对于Thread2来说,它的prev指向HEAD,因此会首先再尝试获取锁一次,如果失败,则会将HEAD的waitStatus值为SIGNAL,下次循环的时候再去尝试获取锁,如果还是失败,且这个时候prev节点的waitStatus已经是SIGNAL,则这个时候线程会被通过LockSupport挂起。

2.  对于Thread3来说,它的prev指向Thread2,因此直接看看Thread2对应的节点的waitStatus是否为SIGNAL,如果不是则将它设置为SIGNAL,再给自己一次去看看自己有没有资格获取锁,如果Thread2还是挡在前面,且它的waitStatus是SIGNAL,则将自己挂起。

如果Thread1死死的握住锁不放,那么Thread2和Thread3现在的状态就是挂起状态啦,而且HEAD,以及Thread的waitStatus都是SIGNAL,尽管他们在整个过程中曾经数次去尝试获取锁,但是都失败了,失败了不能死循环呀,所以就被挂起了。当前状态如下:

**锁释放-等待线程唤起**

我们来看看当Thread1这个时候终于做完了事情,调用了unlock准备释放锁,这个时候发生了什么。

代码:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

解读:

首先,Thread1会修改AQS的state状态,加入之前是1,则变为0,注意这个时候对于非公平锁来说是个很好的插入机会,举个例子,如果锁是公平锁,这个时候来了Thread4,那么这个锁将会被Thread4抢去。。。

我们继续走常规路线来分析,当Thread1修改完状态了,判断队列是否为null,以及队头的waitStatus是否为0,如果waitStatus为0,说明队列无等待线程,按照我们的例子来说,队头的waitStatus为SIGNAL=-1,因此这个时候要通知队列的等待线程,可以来拿锁啦,这也是unparkSuccessor做的事情,unparkSuccessor主要做三件事情:

1.  将队头的waitStatus设置为0.

2.  通过从队列尾部向队列头部移动,找到最后一个waitStatus<=0的那个节点,也就是离队头最近的没有被cancelled的那个节点,队头这个时候指向这个节点。

3.  将这个节点唤醒,其实这个时候Thread1已经出队列了。

还记得线程在哪里挂起的么,上面说过了,在acquireQueued里面,我没有贴代码,自己去看哦。这里我们也大概能理解AQS的这个队列为什么叫FIFO队列了,因此每次唤醒仅仅唤醒队头等待线程,让队头等待线程先出。

**羊群效应**

这里说一下羊群效应,当有多个线程去竞争同一个锁的时候,假设锁被某个线程占用,那么如果有成千上万个线程在等待锁,有一种做法是同时唤醒这成千上万个线程去去竞争锁,这个时候就发生了羊群效应,海量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。**AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。**其实这个思路已经被应用到了分布式锁的实践中,见:Zookeeper分布式锁的改进实现方案。

**总结**

这篇文章粗略的介绍一下ReentrantLock以及锁实现基础框架AQS的实现原理,大致上通过举了个三个线程竞争锁的例子,从lock、unlock过程发生了什么这个问题,深入了解AQS基于状态的标识以及FIFO等待队列方面的工作原理,最后扩展介绍了一下羊群效应问题,博主才疏学浅,还请多多指教。

3. 除了synchronized关键字之外,你是怎么来保障线程安全的?

 见上题!
4. 什么时候需要加volatile关键字?它能保证线程安全吗?

 关于volatile

对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事。
这篇文章将从多个方面来讲解volatile,让你对它更加理解。

计算机中为什么会出现线程不安全的问题

volatile既然是与线程安全有关的问题,那我们先来了解一下计算机在处理数据的过程中为什么会出现线程不安全的问题。
大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中会涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。
为了处理这个问题,在CPU里面就有了高速缓存(Cache)的概念。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
我举个简单的例子,比如cpu在执行下面这段代码的时候,

t = t + 1;

会先从高速缓存中查看是否有t的值,如果有,则直接拿来使用,如果没有,则会从主存中读取,读取之后会复制一份存放在高速缓存中方便下次使用。之后cup进行对t加1操作,然后把数据写入高速缓存,最后会把高速缓存中的数据刷新到主存中。这一过程在单线程运行是没有问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的,本次讲解以多核cup为主)。这时就会出现同一个变量在两个高速缓存中的不一致问题了。例如:

两个线程分别读取了t的值,假设此时t的值为0,并且把t的值存到了各自的高速缓存中,然后线程1对t进行了加1操作,此时t的值为1,并且把t的值写回到主存中。但是线程2中高速缓存的值还是0,进行加1操作之后,t的值还是为1,然后再把t的值写回主存。
此时,就出现了线程不安全问题了。

Java中的线程安全问题

上面那种线程安全问题,可能对于不同的操作系统会有不同的处理机制,例如Windows操作系统和Linux的操作系统的处理方法可能会不同。
我们都知道,Java是一种夸平台的语言,因此Java这种语言在处理线程安全问题的时候,会有自己的处理机制,例如volatile关键字,synchronized关键字,并且这种机制适用于各种平台。
Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
由于java中的每个线程有自己的工作空间,这种工作空间相当于上面所说的高速缓存,因此多个线程在处理一个共享变量的时候,就会出现线程安全问题。

这里简单解释下共享变量,上面我们所说的t就是一个共享变量,也就是说,能够被多个线程访问到的变量,我们称之为共享变量。在java中共享变量包括实例变量,静态变量,数组元素。他们都被存放在堆内存中。

volatile关键字

上面扯了一大堆,都没提到volatile关键字的作用,下面开始讲解volatile关键字是如何保证线程安全问题的。

可见性
什么是可见性?

意思就是说,在多线程环境下,某个共享变量如果被其中一个线程给修改了,其他线程能够立即知道这个共享变量已经被修改了,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取。
例如我们上面说的,当线程1对t进行了加1操作并把数据写回到主存之后,线程2就会知道它自己工作空间内的t已经被修改了,当它要执行加1操作之后,就会去主存中读取。这样,两边的数据就能一致了。
假如一个变量被声明为volatile,那么这个变量就具有了可见性的性质了。这就是volatile关键的作用之一了。

volatile保证变量可见性的原理

当一个变量被声明为volatile时,在编译成会变指令的时候,会多出下面一行:

0x00bbacde: lock add1 $0x0,(%esp);

这句指令的意思就是在寄存器执行一个加0的空操作。不过这条指令的前面有一个lock(锁)前缀。
当处理器在处理拥有lock前缀的指令时:
在之前的处理中,lock会导致传输数据的总线被锁定,其他处理器都不能访问总线,从而保证处理lock指令的处理器能够独享操作数据所在的内存区域,而不会被其他处理所干扰。
但由于总线被锁住,其他处理器都会被堵住,从而影响了多处理器的执行效率。为了解决这个问题,在后来的处理器中,处理器遇到lock指令时不会再锁住总线,而是会检查数据所在的内存区域,如果该数据是在处理器的内部缓存中,则会锁定此缓存区域,处理完后把缓存写回到主存中,并且会利用缓存一致性协议来保证其他处理器中的缓存数据的一致性。

缓存一致性协议

刚才我在说可见性的时候,说“如果一个共享变量被一个线程修改了之后,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取”,实际上是这样的:
线程中的处理器会一直在总线上嗅探其内部缓存中的内存地址在其他处理器的操作情况,一旦嗅探到某处处理器打算修改其内存地址中的值,而该内存地址刚好也在自己的内部缓存中,那么处理器就会强制让自己对该缓存地址的无效。所以当该处理器要访问该数据的时候,由于发现自己缓存的数据无效了,就会去主存中访问。

有序性

实际上,当我们把代码写好之后,虚拟机不一定会按照我们写的代码的顺序来执行。例如对于下面的两句代码:

int a = 1;
int b = 2;

对于这两句代码,你会发现无论是先执行a = 1还是执行b = 2,都不会对a,b最终的值造成影响。所以虚拟机在编译的时候,是有可能把他们进行重排序的。
为什么要进行重排序呢?
你想啊,假如执行 int a = 1这句代码需要100ms的时间,但执行int b = 2这句代码需要1ms的时间,并且先执行哪句代码并不会对a,b最终的值造成影响。那当然是先执行int b = 2这句代码了。
所以,虚拟机在进行代码编译优化的时候,对于那些改变顺序之后不会对最终变量的值造成影响的代码,是有可能将他们进行重排序的。
更多代码编译优化可以看我写的另一篇文章:
虚拟机在运行期对代码的优化策略,那么重排序之后真的不会对代码造成影响吗?实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题的。具体请看下面的代码

public class NoVisibility{
    private static boolean ready;
    private static int number;

    private static class Reader extends Thread{
        public void run(){
        while(!ready){
            Thread.yield();
        }
        System.out.println(number);
    }
}
    public static void main(String[] args){
        new Reader().start();
        number = 42;
        ready = true;
    }
}

这段代码最终打印的一定是42吗?如果没有重排序的话,打印的确实会是42,但如果number = 42和ready = true被进行了重排序,颠倒了顺序,那么就有可能打印出0了,而不是42。(因为number的初始值会是0).
因此,重排序是有可能导致线程安全问题的。

如果一个变量被声明volatile的话,那么这个变量不会被进行重排序,也就是说,虚拟机会保证这个变量之前的代码一定会比它先执行,而之后的代码一定会比它慢执行。
例如把上面中的number声明为volatile,那么number = 42一定会比ready = true先执行。

不过这里需要注意的是,虚拟机只是保证这个变量之前的代码一定比它先执行,但并没有保证这个变量之前的代码不可以重排序。之后的也一样。

volatile关键字能够保证代码的有序性,这个也是volatile关键字的作用。
总结一下,一个被volatile声明的变量主要有以下两种特性保证保证线程安全。

  1. 可见性。
  2. 有序性。
volatile真的能完全保证一个变量的线程安全吗?

我们通过上面的讲解,发现volatile关键字还是挺有用的,不但能够保证变量的可见性,还能保证代码的有序性。
那么,它真的能够保证一个变量在多线程环境下都能被正确的使用吗?
答案是否定的。原因是因为Java里面的运算并非是原子操作

原子操作

原子操作:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
也就是说,处理器要嘛把这组操作全部执行完,中间不允许被其他操作所打断,要嘛这组操作不要执行。
刚才说Java里面的运行并非是原子操作。我举个例子,例如这句代码

int a = b + 1;

处理器在处理代码的时候,需要处理以下三个操作:

  1. 从内存中读取b的值。
  2. 进行a = b + 1这个运算
  3. 把a的值写回到内存中

而这三个操作处理器是不一定就会连续执行的,有可能执行了第一个操作之后,处理器就跑去执行别的操作的。

证明volatile无法保证线程安全的例子

由于Java中的运算并非是原子操作,所以导致volatile声明的变量无法保证线程安全。
对于这句话,我给大家举个例子。代码如下:

public class Test{
    public static volatile int t = 0;
    public static void main(String[] args){
        Thread[] threads = new Thread[10];
        for(int i = 0; i < 10; i++){
            //每个线程对t进行1000次加1的操作
            threads[i] new Thread(new Runnable(){
                @Override
                public void run(){
                    for(int j = 0; j < 1000; j++){
                        t = t + 1;
                    }
                }
            });
            threads[i].start();
        }
        //等待所有累加线程都结束
        while(Thread.activeCount() > 1){
            Thread.yield();
        }
        //打印t的值
        System.out.println(t);
    }
}

 

最终的打印结果会是1000 * 10 = 10000吗?答案是否定的。
问题就出现在t = t + 1这句代码中。我们来分析一下
例如:
线程1读取了t的值,假如t = 0。之后线程2读取了t的值,此时t = 0。
然后线程1执行了加1的操作,此时t = 1。但是这个时候,处理器还没有把t = 1的值写回主存中。这个时候处理器跑去执行线程2,注意,刚才线程2已经读取了t的值,所以这个时候并不会再去读取t的值了,所以此时t的值还是0,然后线程2执行了对t的加1操作,此时t =1 。
这个时候,就出现了线程安全问题了,两个线程都对t执行了加1操作,但t的值却是1。所以说,volatile关键字并不一定能够保证变量的安全性。

什么情况下volatile能够保证线程安全

刚才虽然说,volatile关键字不一定能够保证线程安全的问题,其实,在大多数情况下volatile还是可以保证变量的线程安全问题的。所以,在满足以下两个条件的情况下,volatile就能保证变量的线程安全问题:

  1. 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
  2. 变量不需要与其他状态变量共同参与不变约束

5. 线程池内的线程如果全部忙,提交一个新的任务,会发⽣什么?队列全部塞满了之后,还是忙,再提交会发生什么?

 


6. Tomcat本身的参数你⼀一般会怎么调整?

1、-Xms :表示java虚拟机堆区内存初始内存分配的大小,通常为操作系统可用内存的1/64大小即可,但仍需按照实际情况进行分配。有可能真的按照这样的一个规则分配时,设计出的软件还没有能够运行得起来就挂了。

    2、-Xmx: 表示java虚拟机堆区内存可被分配的最大上限,通常为操作系统可用内存的1/4大小。但是开发过程中,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。

一般来讲对于堆区的内存分配只需要对上述两个参数进行合理配置即可,但是如果想要进行更加精细的分配还可以对堆区内存进一步的细化,那就要用到下面的三个参数了-XX:newSize、-XX:MaxnewSize、-Xmn。当然这源于对堆区的进一步细化分:新生代、中生代、老生代。java中每新new一个对象所占用的内存空间就是新生代的空间,当java垃圾回收机制对堆区进行资源回收后,那些新生代中没有被回收的资源将被转移到中生代,中生代的被转移到老生代。而接下来要讲述的三个参数是用来控制新生代内存大小的。

    1、-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms的值;

    2、-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值; 

    3、-Xmn:至于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过-Xmn来配置新生代的内存大小,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很方便,但需要注意的是这个参数是在JDK1.4版本以后才使用的。

    上面所述即为java虚拟机对外提供的可配置堆区的参数,接下来讲述java虚拟机对非堆区内存配置的两个参数:

    1、-XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)

    2、-XX:MaxPermSize:表示对非堆区分配的内存的最大上限


7. synchronized关键字锁住的是什么东西?在字节码中是怎么表示的?在内存中的对象上表现为什么?

a:对象 b:class文件 c:地址

8. wait/notify/notifyAll⽅方法需不需要被包含在synchronized块中?这是为什么?

要包含,调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁

9. ExecutorService你一般是怎么用的?是每个service放一个还是一个项⽬里面放一个?有什么好处?

当使用 ExecutorService 完毕之后,我们应该关闭它,这样才能保证线程不会继续保持运行状态。举例来说,如果你的程序通过 main() 方法启动,并且主线程退出了你的程序,如果你还有壹個活动的 ExecutorService 存在于你的程序中,那么程序将会继续保持运行状态。存在于 ExecutorService 中的活动线程会阻止Java虚拟机关闭。为了关闭在 ExecutorService 中的线程,你需要调用 shutdown() 方法。ExecutorService 并不会马上关闭,而是不再接收新的任务,壹但所有的线程结束执行当前任务,ExecutorServie 才会真的关闭。所有在调用 shutdown() 方法之前提交到 ExecutorService 的任务都会执行。如果你希望立即关闭 ExecutorService,你可以调用 shutdownNow() 方法。这個方法会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务。但是对于正在执行的任务,是否能够成功关闭它是无法保证的,有可能他们真的被关闭掉了,也有可能它会壹直执行到任务结束。这是壹個最好的尝试


Spring

1.你有没有⽤用过Spring的AOP? 是用来干嘛的? 大概会怎么使用?

 

2.如果⼀一个接口有2个不同的实现, 那么怎么来Autowire一个指定的实现?

3.Spring的声明式事务 @Transaction注解一般写在什么位置? 抛出了异常会自动回滚吗?有没有办法控制不触发回滚?

4.如果想在某个Bean生成并装配完毕后执行自己的逻辑,可以什么方式实现?

5.SpringBoot没有放到web容器里为什么能跑HTTP服务?

6.SpringBoot中如果你想使用自定义的配置文件而不仅仅是application.properties,应该怎么弄?

7.SpringMVC中RequestMapping可以指定GET, POST方法么?怎么指定?
SpringMVC如果希望把输出的Object(例如

 

8.XXResult或者XXResponse)这种包装为JSON输出, 应该怎么处理?

9.怎样拦截SpringMVC的异常,然后做自定义的处理,比如打日志或者包装成JSON

 

Spring 这里有一个 69 道答案版

https://mp.weixin.qq.com/sbiz=MzI3ODcxMzQzMw==&mid=2247486678&idx=1&sn=2a5e38e67c3d267d6c58d963adb24ccc&chksm=eb5389e0dc2400f6f6d2e0eaded591fcfc32575ca227e2cebcf39eec13ff8071a649e494f7d8&scene=21#wechat_redirect

 

MySQL


 

1.如果有很多数据插入MYSQL 你会选择什么方式?

2.如果查询很慢,你会想到的第一个方式是什么?索引是干嘛的?

3.如果建了一个单列索引,查询的时候查出2列,会用到这个单列索引吗?

4.如果建了一个包含多个列的索引,查询的时候只用了第一列,能不能用上这个索引?查三列呢?

5.接上题,如果where条件后面带有一个 i + 5 < 100 会使用到这个索引吗?

6.怎么看是否用到了了某个索引?

7.like %aaa%会使用索引吗? like aaa%呢?
drop、truncate、delete的区别?

8.平时你们是怎么监控数据库的? 慢SQL是怎么排查的?

9.你们数据库是否支持emoji表情,如果不支持,如何操作?

10.你们的数据库单表数据量是多少?一般多大的时候开始出现查询性能急剧下降?

11查询死掉了,想要找出执行的查询进程用什么命令?找出来之后一般你会干嘛?

12.读写分离是怎么做的?你认为中间件会怎么来操作?这样操作跟事务有什么关系?

13.分库分表有没有做过?线上的迁移过程是怎么样的?如何确定数据是正确的?

 

JVM


 

1.你知道哪些或者你们线上使用什么GC策略? 它有什么优势,适用于什么场景?

 

2.JAVA类加载器包括几种?它们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?

3.如何自定义一个类加载器?你使用过哪些或者你在什么场景下需要一个自定义的类加载器吗?
堆内存设置的参数是什么?

4.Perm Space中保存什么数据? 会引起OutOfMemory吗?

5.做gc时,一个对象在内存各个Space中被移动的顺序是什么?

6.你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理过程中有哪些收获?

7.1.8之后Perm Space有哪些变动?MetaSpace大小默认是无限的么? 还是你们会通过什么方式来指定大小?

8.Jstack是干什么的? Jstat呢? 如果线上程序周期性地出现卡顿,你怀疑可能是gc导致的,你会怎么来排查这个问题?线程日志一般你会看其中的什么部分?


9.StackOverFlow异常有没有遇到过?一般你猜测会在什么情况下被触发?如何指定一个线程的堆栈大小?一般你们写多少?

 

Linux命令


 

1.日志特别大只想看最后100行怎么弄弄? 如果想一直看日志的持续输出,用什么命令?

 

2.如果日志一边输出,一边想实时看到有没有某个关键字应该怎么弄?

3.grep如果忽略大小写应该怎么弄? 正则表达式呢?

4.vim往下一行是什么键?往下30行呢? 跳到文件末尾一行是什么? 跳回来是什么? 向后搜索是什么?

5.如果有个文本文件,按空格作为列的分隔符,如果想统计第三列里面的每个单词的出现次数应该怎么弄?

6.如果把上面的出现次数排个序应该怎么弄? 想按照数字本身的顺序而不是字符串的顺序排列怎么弄?

7.Linux环境变量是以什么作为分隔符的?环境变量通过什么命令设置?

8.给某个文件权设置限比如设置为64 是用什么命令?这个6是什么意思?

9.Linux下面如果想看某个进程的资源占用情况是怎么看的?系统load大概指的什么意思?你们线上系统load一般多少?如果一个4核机器,你认为多少load是比较正常的?top命令里面按一下1会发生什么?

10.top命令里面,有时候所有进程的CPU使用率加起来超过100%是怎么回事?

11.还有哪些查看系统性能或者供你发现问题的命令?你一般是看哪个参数?

12.想看某个进程打开了哪些网络连接是什么命令?里面连接的状态你比较关心哪几种? -- 偏题
有没有做过Linux系统参数方面的优化,大概优化过什么?

13.系统参数里面有个叫做backlog的可以用来干什么?

14.查看网络连接发现好多TIMEWAIT 可能是什么原因?对你的应用会有什么影响?你会选择什么样的方式来减少这些TIMEWAIT

15.可否介绍一下TCP三次握手的过程,如果现在有个网络程序,你用第三方的library来发送数据,你怀疑这个library发送的数据有问题,那么怎么来验证?tcpdump导出的文件你一般是怎么分析的?

16.KeepAlive是用来干什么的?这样的好处是什么?

 

Redis -- 开发


 

1.缓存穿透可以介绍一下么?你认为应该如何解决这个问题?

 

2.你是怎么触发缓存更新的?(比如设置超时时间(被动方式), 比如更新的时候主动update)?如果是被动的方式如何控制多个入口同时触发某个缓存更新?

3.你们用Redis来做什么?为什么不用其他的KV存储例例如Memcached,Cassandra等?

4.你们用什么Redis客户端? Redis高性能的原因大概可以讲一些?

5.你熟悉哪些Redis的数据结构? zset是干什么的? 和set有什么区别?

6.Redis的hash, 存储和获取的具体命令叫什么名字?

7.LPOP和BLPOP的区别?

8.Redis的有一些包含SCAN关键字的命令是干嘛的? SCAN返回的数据量是固定的吗?

9.Redis中的Lua有没有使用过? 可以用来做什么? 为什么可以这么用?

10.Redis的Pipeline是用来干什么的? -- 运维
Redis持久化大概有几种方式? aof和rdb的区别是什么? AOF有什么优缺点吗?

11.Redis Replication的大致流程是什么? bgsave这个命令的执行过程? -- 偏题

12.如果有很多 KV数据要存储到Redis, 但是内存不足, 通过什么方式可以缩减内存? 为什么这样可以缩小内存?

13.Redis中List, HashTable都用到了ZipList, 为什么会选择它?

 

Redis 这里有一个 50 道答案版的

https://mp.weixin.qq.com/sbiz=MzI3ODcxMzQzMw==&mid=2247486734&idx=2&sn=d8454c6cbd09ab60ef5a728a36c19e8c&chksm=eb538838dc24012e15b813df90a115803c243eb6a242c052d684c6c3819b5f3300f3a2d482be&scene=21#wechat_redirect

 

监控、稳定性


 

1.业务日志是通过什么方式来收集的?

 

2.线上机器如何监控?采用什么开源产品或者自研的产品?它是分钟级的还是秒级的?

3.如果让你来想办法收集一个JAVA后端应用的性能数据,你会在意哪些方面? 你会选择什么样的工具、思路来收集?

 qi

4.一般你调用第三方的时候会不会监控调用情况?

 

阿里面题

1.hashcode相等两个类一定相等吗?equals呢?相反呢?

2.介绍一下集合框架?

3.hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢?

4.hashmap和treemap什么区别?低层数据结构是什么?

5.线程池用过吗都有什么参数?底层如何实现的?

6.sychnized和Lock什么区别?sychnize 什么情况情况是对象锁? 什么时候是全局锁为什么?

7.ThreadLocal 是什么底层如何实现?写一个例子呗?

8.volitile的工作原理?

9.cas知道吗如何实现的?

10.请用至少四种写法写一个单例模式?

11.请介绍一下JVM内存模型??用过什么垃圾回收器都说说呗

12.线上发送频繁full gc如何处理? CPU 使用率过高怎么办?

13.如何定位问题?如何解决说一下解决思路和处理方法

14.知道字节码吗?字节码都有哪些?Integer x =5,int y =5,比较x =y 都经过哪些步骤?

15.讲讲类加载机制呗都有哪些类加载器,这些类加载器都加载哪些文件?

16.手写一下类加载Demo

17.知道osgi吗? 他是如何实现的???

18.请问你做过哪些JVM优化?使用什么方法达到什么效果???

19.classforName("java.lang.String")和String classgetClassLoader() LoadClass("java.lang.String") 什么区别啊?

20.探查Tomcat的运行机制即框架?

21.分析Tomcat线程模型?

22.Tomcat系统参数认识和调优?

23.MySQL底层B+Tree机制?

24.SQL执行计划详解?

25.索引优化详解?

26.SQL语句如如如何优化?

27.spring都有哪些机制啊AOP底层如何实现的啊IOC呢??

28.cgLib知道吗?他和jdk动态代理什么区别?手写一个jdk动态代理呗?

29.使用mysq1索引都有哪些原则? ?索引什么数据结构? 3+tree 和B tree 什么区别?

30.MySQL有哪些存储引擎啊?都有啥区别? 要详细!

31.设计高并发系统数据库层面该怎么设计??数据库锁有哪些类型?如何实现呀?

32.数据库事务有哪些?

33.如何设计可以动态扩容缩容的分库分表方案?

34.用过哪些分库分表中间件,有啥优点和缺点?讲一下你了解的分库分表中间件的底层实现原理?

35.我现在有一个未分库分表的系统,以后系统需分库分表,如何设计,让未分库分表的系统动态切换到分库分表的系统上?TCC? 那若出现网络原因,网络连不通怎么办啊?

36.分布式事务知道吗? 你们怎么解决的?

37.为什么要分库分表啊?

38.RPC通信原理,分布式通信原理

39.分布式寻址方式都有哪些算法知道一致性hash吗?手写一下java实现代码??你若userId取摸分片,那我要查一段连续时间里的数据怎么办???

40.如何解决分库分表主键问题有什么实现方案??

41.redis和memcheched 什么区别为什么单线程的redis比多线程的memched效率要高啊?

42.redis有什么数据类型都在哪些场景下使用啊?

43.reids的主从复制是怎么实现的redis的集群模式是如何实现的呢redis的key是如何寻址的啊?

44.使用redis如何设计分布式锁?使用zk可以吗?如何实现啊这两种哪个效率更高啊??

45.知道redis的持久化吗都有什么缺点优点啊? ?具体底层实现呢?

46.redis过期策略都有哪些LRU 写一下java版本的代码吧??

47.说一下dubbo的实现过程注册中心挂了可以继续通信吗??

48.dubbo支持哪些序列化协议?hessian 说一下hessian的数据结构PB知道吗为啥PB效率是最高的啊??

49.知道netty吗'netty可以干嘛呀NIO,BIO,AIO 都是什么啊有什么区别啊?

50.dubbo复制均衡策略和高可用策略都有哪些啊动态代理策略呢?

51.为什么要进行系统拆分啊拆分不用dubbo可以吗'dubbo和thrift什么区别啊?

52.为什么使用消息队列啊消息队列有什么优点和缺点啊?

53.如何保证消息队列的高可用啊如何保证消息不被重复消费啊

54.kafka ,activemq,rabbitmq ,rocketmq都有什么优点,缺点啊???

55.如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路

56.说一下TCP 'IP四层?

57.的工作流程?? ?http1.0 http1.1http2.0 具体哪些区别啊?

58.TCP三次握手,四层分手的工作流程画一下流程图为什么不是四次五次或者二次啊?

59.画一下https的工作流程?具体如何实现啊?如何防止被抓包啊??

60.源码中所用到的经典设计思想及常用设计模式

61.系统架构如何选择合适日志技术(log4j、log4j2、slf4j、jcl…….)

62.springAOP的原理,springAOP和Aspectj的关系,springAOP的源码问题

63.dubbo框架的底层通信原理

64.RPC通信原理,分布式通信原理

65.如何利用springCloud来架构微服务项目

66.如何正确使用docker技术

67.springMVC的底层原理、如何从源码来分析其原理

68.mybaits的底层实现原理,如何从源码来分析mybaits

69.mysql的索引原理,索引是怎么实现的

70.索引的底层算法、如何正确使用、优化索引

71.springboot如何快速构建系统

72.zk原理知道吗zk都可以干什么Paxos算法知道吗?说一下原理和实现?

73.如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路

74.分布式事务知道吗? 你们怎么解决的?

75.请问你做过哪些JVM优化?使用什么方法达到什么效果

本文摘自https://www.cnblogs.com/lwh-note/p/9495954.html

转载于:https://www.cnblogs.com/swallower/p/11459007.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值