Java常见面试题——多线程

 

3.2.1 多线程

线程有哪些状态?各种状态之间是怎么切换的?

答:

Java的Thread类中通过static枚举定义了线程的几种状态

NEW:初始,即新创建了一个线程对象,但是还没有start()

RUNNABLE:就绪/运行,调用start()成功后的状态

BLOCKED:阻塞,即线程阻塞于锁

WAITING:线程需要等待其它线程做出一些特定动作(通知或中断)

TIMED_WAITING:指定时间的等待

TERMINATED:结束

 

1.Thread、Runnable、Callable三种方式有什么区别和联系?

答:Thread类实现了Runable接口仅有的run()方法

Callable接口与Runnable有什么联系?

2.volatile保证不了线程安全,那么它能干什么?

拓展1:volatile在什么场景使用?

因为volatile只保证可见性而不保证原子性,因而在不符合如下两条规则时仍然需要通过加锁(synchronized或java.util.concurrent的原子类)来保证原子性:

(1)运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值

(2)变量不需要与其它的状态变量共同参与不变约束

像i++这种非线程安全的操作,通过synchronized修饰,即能保证原子性

CAS:比较并交换。参考《深入理解Java虚拟机》13.2.2的2非阻塞同步

CAS是一种无锁算法,包括3个操作数,内存值V,旧的预期值A,需要修改的新值B。在修改前比较旧的预期值和A和内存值V,若相同则修改为新值,否则放弃之前的修改操作。

具体过程是,线程将变量值拷贝一份到工作内存,在修改前将这个拷贝值与主内存值进行比较(可能主内存中的值已经被别的线程修改了),若相等,则修改,不等则放弃操作,重新执行。这里的从主内存刷新要求了可见性,即CAS需要与volatile变量配合。

CAS在JDK中的应用如:java.util.concurrent.atomic.AtomicInteger的incrementAndGet()。

public final int incrementAndGet() {

        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)

  {

    int i;

    do

    {

      i = getIntVolatile(paramObject, paramLong);

    } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));

    return i;

  }

拓展2:volatile如何与CAS结合保证原子性?

 

什么是QAS?

答:AbstractQueuedSynchronizer。如果说CAS是java.util.concurrent的基础,那么AQS就是整个Java并发包的核心,ReentrantLock、CountDownLatch、Semaphore等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比如说ReentrantLock,所有等待的线程被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。

AQS定义了双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,若有需要,可以重写这2个方法以实现自己的并发功能。

3.什么是竞态条件?

答:由于多线程对共享资源的竞争便会产生竞态条件,如果需要先执行的程序在竞争资源失败被排到了后面,那么程序执行就可能会出现意想不到的错误。这种错误会重复出现,但很难发现,因为竞争是随机的。

 

4.Thread的stop()、resume()、suspend()等方法都被标记为过期的,为什么?或者说除了这些方法还提供了什么停止等控制线程的API?

答:早期的stop()等方法由于存在潜在的死锁风险而被弃用了,之后Java没有提供兼容并且线程安全的停止线程的API。在run()或call()运行完后线程会自动结束,如果需要手动终止一个线程,可以用volatile来修饰一个boolean变量,来退出run()的循环或取消任务从而达到中止线程的目的。

拓展1:具体的手动中止线程的方式如何实现?

 

5.为什么wait()、notify()、notifyAll()等方法是在Object对象中,而不是Thread中?

答:这是一个设计问题,这些方法只有在Object中而不是Thread中才有意义。一个显而易见却又容易忽视的原因是,在Java中,锁在资源共享时才有意义,从语言层面来说,最直观的是对象,而这些方法都是锁级别的操作。也就是说在Java语言中,锁都是对象的锁,而没有线程的锁,而线程等待的也是某个对象的锁被释放。如果这些方法在Thread中,线程等待的是哪个线程的锁就不合理了。

 

6.线程间通信有哪些方式?各有什么区别?分别是怎么使用的?

 

7.什么是ThreadLocal?

答:ThreadLocal是Java中一个特殊的变量,每个线程都有,它能够帮助创建代价比较高昂的对象获得线程安全,比如SimpleDateFormat、ThreadLocalRandom……每个Thread中维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,对数据进行隔离,从而实现线程安全。

 

8.什么是FutureTask ?怎么用?

答:FutureTask实现了Runnable接口(实现的RunnableFuture接口继承了Runnable接口),所以它可以提交给Executor来执行……

使用举例:

ClassImplementsOfCallable ca1 = new ClassImplementsOfCallable (params);

           FutureTask result = new FutureTask<>(ca1);

           Thread call1 = new Thread(result);

           call1.start();

这里result为执行的返回结果

9.为什么wait()、notify()要在同步代码块中调用?

答:Java API强制要求,避免wait()和notify()产生竞态条件。这些方法使用的前提是必须先获得对象的锁

 

10.为什么应该在循环中检查等待条件?

 

11.如何解决生产者消费者问题?

答:即线程间通信的问题,最简单的是wait()、notify()方式,比较好的有Semaphore、BlockingQueue等方式……

 

12.如何避免死锁?

答:

死锁发生的4个条件:

(1)互斥条件:一个资源每次只能被一个进程使用

(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持而不释放

(3)不剥夺条件:进程已获得的资源,在未使用完前,不允许强行剥夺

(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

避免死锁的最简单办法是阻止循环等待,将系统中所有的资源设置标志位,排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

 

13.Java中活锁和死锁有什么区别?

答:处于活锁的线程或进程的状态是可以改变的,只是不能继续执行。比如两个线程相互谦让资源会出现线程状态不断来回切换,但状态切换的方向是一致的,导致资源并没有被占用,但是线程也无法继续执行……

 

14.Thread类的holdsLock()方法可以检测线程是否拥有锁,拥有时返回true,是个本地方法

 

15.如何获取Java的线程堆栈?

答:Windows:ctrl+break;Linux:kill -3;jstack工具

 

16.synchronized和ReenTrantLock的区别?

答:synchronized是关键字,而ReentrantLock是Java5之后才引进的类,实现了Lock接口。ReentrantLock更加灵活和可扩展,扩展性体现如:

(1)ReentrantLock可以对获取锁的等待时间进行设置,避免死锁;而synchronized尝试获取锁不能中途取消

(2)ReentrantLock可以获取各种锁的信息

(3)ReentrantLock可以灵活地实现多路通知

二者实现锁的机制也并不相同。ReentrantLock底层调用的是Unsafe的park()方法,而synchronized操作的是对象头信息。

 

16-1.synchronized和ReentrantLock有什么区别?有人说synchronized最慢,这话靠谱么?

典型回答:synchronized是Java内建的同步机制,所以也有Intrinsic Locking的说法,提供了互斥的语义和可见性,当一个线程已经获得当前锁时,其它试图获取的线程只能等待或阻塞。

在Java5之前,synchronized是仅有的同步手段,可以修饰方法,代码块。

ReentrantLock通常译为再入锁,是Java5提供的锁实现,语义和synchronized基本相同。再入锁通过调用lock()方法获取,书写更灵活。ReentrantLock提供了很多实用的方法,能够实现很多synchronized无法做到的细节控制,如可以控制公平性(fairness),或者利用定义条件等。同时需要注意的是,必须明确调用unlock()方法释放锁,不然会一直持有该锁。

synchronized和ReentrantLock的性能不能一概而论,早期版本的synchronized在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于ReentrantLock。

 

知识点:

(1)什么是线程安全?

(2)synchronized、ReentrantLock等机制的基本使用与案例

(3)synchronized、ReentrantLock底层实现;锁膨胀、降级;偏斜锁、自旋锁、轻量级锁、重量级锁等

(4)java.util.concurrent.lock各种不同实现和案例分析

 

 

17.yield()方法的作用?

答:暂停当前线程,静态方法,只能保证当前线程放弃CPU,但并不能保证其它线程获得CPU,因为当前线程在失去CPU使用权后可能又在下一次竞争中获得

 

18.ConcurrentHashMap的并发度?

答:ConcurrentHashMap把实际Map划分成若干份来实现可扩展性和线程安全,这种划分是通过并发度获得的,是构造函数的一个参数,默认为16,意味着默认最大支持16个线程同时操作。

 

19.Swing是线程安全的吗?为什么?

答:Swing不是线程安全的,Swing的组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成……

 

20.invokeAndWait()和invokeLater()有什么区别?

 

21.Swing API中哪些方法是线程安全的?

 

22.如何创建Immutable对象?

 

23.ReadWriteLock?(ReentrantReadWriteLock)

答:ReadWriteLock是Java5引入的接口,ReentrantReadWriteLock是实现了这个接口的读写锁,读锁是共享的,写锁是独占的,读和读不会互斥,读和写,写和读,写和写会互斥。

 

24.多线程中的忙循环是什么?

答:忙循环是开发者通过循环的方式让一个线程等待,与wait()、sleep()、yield()不同的是,忙循环不会放弃CPU使用权,运行的是一个空循环。目的是保留CPU缓存,在多核系统中,一个等待线程被唤醒后可能在另一个内核中了,这样需要重建缓存,忙循环能够避免重建缓存,减少等待重建的时间。

 

25.volatile变量和atomic变量有何不同?

答:AtomicInteger类提供的atomic方法具有原子性,而volatile不保证原子性

 

26.如果同步块内的线程抛出异常会发生什么?

答:无论同步块是正常还是异常退出,线程都会释放锁……

拓展1:锁接口提供的方法异常会自动释放锁么?或则还需要开发者如何处理?

 

27.双检锁的单例模式以及隐患?线程安全的Singleton?

JVM类加载和静态变量初始化创建Singleton、使用枚举常见Singleton……

 

28.多线程最佳实践?

(1)给线程取个有意义的名字:给线程取一个和它要完成的任务相关的名字,方便排查问题和跟踪。目前主要的框架和JDK都遵循这个原则。

(2)避免锁定和缩小同步的范围:锁需要上下文切换,耗费时间和空间。应最低限度使用同步和锁,缩小临界范围。

(3)多用同步类,少用wait()和notify():如CountDownLatch、Semaphore、CyclicBarrier、Exchanger等同步类简化了编码,而wait()、notify()很难实现对复杂控制流的控制,另外同步类在JDK中不断优化。

(4)多用并发集合少用同步集合:并发集合比同步集合的可扩展性更好……

 

29.如何强制启动一个线程?

答:线程由线程调度器控制,Java没有提供强制启动线程的API……

 

30.fork join框架?(工作窃取算法)

 

31.wait()和sleep()有什么不同?

答:wait()是Object类的方法,而sleep()是Thread类的方法;wait()用于线程间通信,会释放锁,而sleep()仅释放CPU,不释放锁。

 

32.Java中的Semaphore是什么?

答:Java中的Semaphore是一种新的同步类,是一个计数信号。从概念上,信号量维护了一个许可集合,如有必要,在许可可用前会阻塞每一个acquire(),然后再获取许可。每个release()添加一个许可,从而释放一个正在阻塞的获取者,但是,不使用实际的许可对象,Semaphore只对许可可用的号码进行计数,并采取相应行动。信号量常用于多线程,如数据库连接池中。

Semaphore是信号量,用于限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出n,则等待,此处若把n设为1则相当于synchronized。

 

33.Java中使用的线程调度算法是什么?

答:抢占式。一个线程释放CPU后,操作系统根据线程优先级、线程饥饿情况等数据计算出一个优先级,并分配下一个时间片给优先级最高的线程。

 

34.Thread.sleep(0)的作用是什么?

答:由于Java采用抢占式的线程调度算法,因此可能出现某条线程常常获得CPU控制权的情况,而某些优先级较低的线程一直获取不到CPU的控制权,通过Thread.sleep(0)可以手动触发一次暂时放弃CPU,重新竞争CPU的机会,作用在于平衡CPU的控制权。sleep(0)后线程进入就绪态,而不是等待态,所以可以继续竞争CPU。

 

线程类的构造方法、静态块是被哪个线程调用的?

答:线程类的构造方法、静态块是被new这个线程的线程所调用的,run()才是被线程本身调用的。即谁创建这个线程,谁负责调用其构造方法和静态块。

CycliBarrier和CountDownLatch

CycBarrier和CountDownLatch的区别?

都可以表示代码运行到某个点上,区别在于:

(1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点之后,只是给某个数值-1而已,该线程继续运行

(2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务

(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了

3.2.2 线程池

1.什么是线程池?为什么要使用它?

 

拓展1:都说创建线程要花费昂贵的资源和时间,具体是什么数量级?

答:线程消耗包括内存和其它系统资源在内的大量资源,除了Thread对象所需的内存之外,每个线程都需要2个可能很大的执行调用堆栈,除此之外,JVM可能会为每个Java线程创建一个本机线程,这些本机线程将消耗额外的系统资源。线程之间切换的调度开销很小,但是对于服务器程序,可能线程的数量是庞大的,而且调度很频繁,那么切换也可能严重地影响程序的性能。

3、线程池有什么作用?

4、说说几种常见的线程池及使用场景。

5、线程池都有哪几种工作队列?

 

6、怎么理解无界队列和有界队列?

7、线程池中的几种重要的参数及流程说明。

8.线程池的死锁

答:对于需要返回结果的线程,存在一种情况,所有池线程都在执行已阻塞的等待队列中另一个任务的执行结果,但是阻塞队列已满,这个任务一直得不到空闲线程来执行,而导致线程池的死锁。

场景:线程之间有较多的交互,比如相互之间发送查询,而查询作为排队的任务执行,且查询对象同步等待。

 

 

 

1.应用容器已经有提供线程池?还需要自实现线程池么?

初步理解:容器管理了外部访问,容器的线程池主要解决的是高并发的外部访问,能够同时接受较多的访问请求,但是业务处理部分是服务提供方实现的,这部分可能存在性能瓶颈,另外由应用容器起的线程在执行到业务代码时,仍然可能引起资源竞争。

2.线程池怎么用?以什么形式提供?

答:Java自带了线程池的实现

如下即可创建一个线程池实例

java.util.concurrent.Executors;

java.util.concurrent.ExecutorService;

java.util.concurrent.CompletionService;

参考:https://blog.csdn.net/zy_281870667/article/details/72047325

  MyThread mt = null;

  ExecutorService es = Executors.newCachedThreadPool();//线程池

  CompletionService cs = new ExecutorCompletionService(es);

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

  mt = new MyThread (i+1);

  cs.submit(mt);

  }

3.连接池由谁实现?

答:连接池可以由J2EE容器实现,也可以由数据库管理框架(如Mybatis)实现,也可以应用自实现。Mybatis的实现使用了代理模式,获取和关闭的连接对象其实是经过包装的对象。

4.有哪几种线程池,有什么区别和联系?分别有什么差异和适用的场景?

答:java.util.concurrent.Executors;

(1)newCachedThreadPool

(2)newFixedThreadPool

(3)newSingleThreadExecutor

(4)newSingleThreadExecutor

 

拓展1:这些线程池与ThreadPoolExecutor有什么关联?

答:上述4种线程池实际上都是通过返回ThreadPoolExceutor实例实现的,也就是说ThreadPoolExecutor是线程池的实现,提供了不同的参数和构造函数,能够返回不同功能的线程池实例。

newFiexdThreadPool

创建固定数量的线程池,核心线程数和最大线程数固定,工作队列为最大为Integer.MAX_VALUE大小的阻塞队列。当执行任务时,如果线程都忙,就会丢到工作队列等待有空闲线程来执行,若队列满则执行默认的拒绝策略。

public static ExecutorService newFixedThreadPool(int nThreads) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue());

}

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue(),

                                      threadFactory);

}

 

newCachedThreadPool

带缓冲线程池,核心线程数为0,最大线程数为Integer的最大值,超过0个的空闲线程在60秒后销毁,SynchronousQueue是一个可直接提交的队列,当有新任务时,若有空闲线程则执行任务,若没有则创建一个线程,适合执行速度快而且小的线程,否则超出最大线程池边界会造成内存溢出。

public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue());

}

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue(),

                                      threadFactory);

}

 

newSingleThreadExecutor

单线程线程池,核心线程数和最大线程数均为1,每次只执行一个线程,多余的先存储到工作队列。

public static ExecutorService newSingleThreadExecutor() {

        return new FinalizableDelegatedExecutorService

            (new ThreadPoolExecutor(1, 1,

                                    0L, TimeUnit.MILLISECONDS,

                                    new LinkedBlockingQueue()));

}

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {

        return new FinalizableDelegatedExecutorService

            (new ThreadPoolExecutor(1, 1,

                                    0L, TimeUnit.MILLISECONDS,

                                    new LinkedBlockingQueue(),

                                    threadFactory));

}

 

newScheduledThreadPool

调度线程池,按一定周期执行任务

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {

        return new ScheduledThreadPoolExecutor(corePoolSize);

}

public static ScheduledExecutorService newScheduledThreadPool(

            int corePoolSize, ThreadFactory threadFactory) {

        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);

}

拒绝策略

(1)AbortPolicy

默认的拒绝策略,直接抛出拒绝异常

(2)CallerRunsPolicy

如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。

(3)DiscardPolicy

丢弃,即不处理新任务

(4)DiscardOldestPolicy

抛弃最老的任务,从队列取出最老的任务然后放入新的任务进行执行

关闭线程池

shutdown():不再接受新任务,之前提交的任务等执行结束再关闭线程池。

shutdownNow():不再接受新任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程列表。

5.线程池的submit()和execute()方法有什么区别和联系?

答:都可以向线程池提交任务。

execute()定义在Executor接口中,返回为void;

submit()定义在ExecutorService接口中,继承自Executor,返回持有计算结果的Future对象,并且能通过Future的get方法捕获线程中的异常。其它线程池类如ThreadPoolExecutor、ScheduledThreadPoolExecutor都有这些方法。

6.如果提交任务时,线程队列已满,会发生什么?

答:

(1)如果使用的是无界队列LinkedBlockingQueue,那么继续添加任务到阻塞队列中等待执行,LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务

(2)如果使用的是有界队列如ArrayBlockingQueue,那么任务会先被添加,然后根据maximumPoolSize的值增加线程数量,如果增加了线程数量仍然处理不过来,则会使用拒绝策略RejectedExceptionHandler处理满了的任务,默认是AbortPolicy

7.高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务又如何?并发高、任务执行时间长的业务又如何?

(1)高并发、任务执行时间短的业务,线程池线程数可设置为CPU+1,减少线程上下文切换

(2)并发不高、任务执行时间长的业务需要区分看

(a)如果执行时间主要集中在IO,即IO密集型任务,因IO操作不占用CPU,所以不应让CPU空闲,适合加大线程数,让CPU处理更多任务

(b)如果执行时间主要集中在计算,即计算密集型任务,则和(1)一样,减少线程上下文切换

(3)并发高、任务执行时间长的场景关键不在于线程池而在于整体架构的设计,如第一步考虑某些业务数据是否能做缓存,第二步考虑服务器扩容。针对执行时间长的问题,考虑使用中间件进行拆分和解耦。

 

3.2.2.1 ThreadPoolExecutor

corePoolSize:核心线程池大小

maximumPoolSize:最大线程池大小。

keepAliveTime:如果当前线程池中的线程数超过了corePoolSize,那么如果在keepAliveTime时间内都没有新的任务需要处理,则超过corePoolSize部分的线程会被销毁,默认情况是不会回收core线程的。可通过allowCoreThreadTimeOut改变这一行为。

workQueue:实际用于存储任务的队列。最核心的参数,直接决定了线程池的行为。是否扩容和core、max设置,队列类型相关。

threadFactory:创建线程的工厂。若不指定,默认使用Executors.defaultThreadFactory(),一般会在此设置线程名,异常处理器等。

handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。

线程池状态:

RUNNING:当前线程池正在运行中,可以接受新任务以及处理队列中的任务。

SHUTDOWN:不再接受新的任务,但会继续处理队列中的任务。

STOP:不再接受新任务,也不处理队列中的任务,并且会中断正在进行的任务。

TIDYING:所有任务都已处理完毕,线程数为0,转为TIDYING状态后,会调用terminated()回调

TERMINATED:terminated()已执行完毕。

 

8、什么是反射机制?

9、说说反射机制的作用。

10、反射机制会不会有性能问题?

20、什么是虚拟主机及实现原理?

21、什么是Java虚拟机,为什么要使用?

22、说说Java虚拟机的生命周期及体系结构。

23、说一说Java内存区域。

24、什么是分布式系统?

25、分布式系统你会考虑哪些方面?

26、讲一讲TCP协议的三次握手和四次挥手流程。

答:

三次握手

第一次:连接请求。客户端发送连接请求报文,SYN=1,Seq=X,然后客户端进入SYN_SEND状态,等待服务器确认。

第二次:服务器响应连接请求。服务器收到客户端的连接请求后,SYN=1,ack=X+1,Seq=Y,服务器进入SYN_RECV状态。

第三次:客户端响应请求。客户端将,ack=Y+1,Seq=Z,客户端进入ESTABLISHED状态。

四次挥手

第一次:某一方(可以是客户端,也可以是服务端)主动请求关闭连接。A发起关闭连接请求,FIN=1,seq=m,发送完后进入FIN_WAIT_1状态,不再发送数据,但是可以接收。

第二次:另一方响应关闭请求。B收到关闭请求后向A发送响应,SYN=1,seq=n,ack=m+1,进入CLOSE_WAIT状态。

第三次:被动方发起关闭连接请求。B向A发起关闭请求,FIN=1,SYN=1,seq=w,ack=m+1,进入LAST_ACK状态(B在收到A的关闭请求后,不一定会立即发起关闭请求,可能数据还没传输完,仍然需要发送数据)。

第四次:主动方响应并进入等待状态。A收到FIN后发送响应,SYN=1,seq=m+1,ack=w+1,进入TIME_WAIT状态,B收到响应后进入CLOSED状态。(此处主动方需要等待2MSL时间才关闭)

27、为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接?

答:如果是两次,那么存在这样一种情况,客户端某一次发送的连接请求并没有丢失,而是因为某个网络堵塞或其它原因导致这个请求延误,在连接释放之后才到达服务端,那么服务端会误以为这是一个新的连接请求而发出响应报文,如果是两次握手,那么此时连接就已经建立了。然而事实上客户端并没有发送新的连接请求,在收到服务端的响应后不会理睬,也不会发送数据,此时服务端会一直等待客户端的数据,导致服务端浪费很多资源。采用三次握手的方式能够有效避免这种场景。

28、为什么TCP TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

答:MSL即报文最大生存时间,即报文在被丢弃前在网络的最大有效时间。

原因1:防止被动方收不到影响而反复发生结束连接请求。如果主动方直接进入CLOSED状态,而被动方可能因为网络或其它原因没有收到主动方的这个响应,而重新发起关闭连接请求,但是主动方已经是CLOSED状态,不可能再响应,被动方无法得知何时关闭请求。

原因2:避免新连接与旧连接的迟到数据混淆。可能网络中仍然存在当前连接的迟到数据,如果直接CLOSED,那么被动方在收到这个数据时会误以为是被动方的新连接的数据而保持连接,实际上却收不到对方的数据而导致连接继续保存,浪费资源。(在Linux等系统中,一个端口不能被重复打开,一个TCP连接在TIME_WAIT状态时,端口仍然被占用,无法使用该端口建立新的连接)

29、什么是DoS、DDoS、DRDoS攻击?如何防御?

30、描述一下Java异常层次结构。

31、什么是检查异常,不受检查异常,运行时异常?并分别举例说明。

32、finally块一定会执行吗?

答:不一定。

(1)若在finally对应的try执行之前返回了,则不会执行finally。比如try之前异常了,或者try之前执行了return。

(2)若在finally对应的try之前或之中执行了System.exit(0),则不会执行finally,此时会直接停止,即使是在一个有返回的函数中也不会返回。

(3)线程被中断或杀死时,finally也可能不执行。极端情况如死机、断电等,finally也可能不会执行。

可参考:https://blog.csdn.net/jiasanshou/article/details/6996932,经验证与博客中描述一致

33、正常情况下,当在try块或catch块中遇到return语句时,finally语句块在方法返回之前还是之后被执行?

答:之前。

34、try、catch、finally语句块的执行顺序。

答:

(1)正常情况下,先执行try语句块,若顺利执行没有出现异常则在之后执行finally,若出现异常则跳到catch中,try中异常后面的语句不执行,在catch执行完执行finally。

(2)如果try或catch中有控制转移语句(return、throw、break、contine),finally会在控制转移语句之前执行,比如return,finally会在return之前执行。

(3)控制转移语句分为有返回值和无返回值的,或者说控制权转移和保留之分。return和throw为有返回值的,也是控制权会转移给调用者的。如果try中有需要返回的语句,则在执行finally之前会将返回值保留到局部变量表,在执行完finally之后,再将这个值恢复到操作数栈中,这样即使在finally中修改了这个返回值,最后返回给调用者的也是finally执行之前的值;但如果finally中也有返回,则返回的是finally块的值而不是try中的。

注意:如果try中return的是一个方法,eg:return test();那么效果等同于Object tmp =test();return tmp;也就是说执行顺序是先执行test()代码部分,然后是finally部分,最后是try的return。

拓展1:根据《The JavaTM Programming Language,Forth Edith》的描述,finally的执行总是有原因的,可能是try块顺利的执行了,但try中出现return或throw的控制流,此时进入finally的原因会被保留直到finally被执行完,而如果finally中本身也出现了如return、break、抛异常的控制流,那么初始进入finally的原因会被新的取代。

35、Java虚拟机中,数据类型可以分为哪几类?

36、怎么理解栈、堆?堆中存什么?栈中存什么?

37、为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?

38、在Java中,什么是是栈的起始点,同是也是程序的起始点?

39、为什么不把基本类型放堆中呢?

40、Java中的参数传递时传值呢?还是传引用?

答:值传递,我们通常通过new获得的是栈中对象的引用,这个引用其实是对象的地址,作为参数传递时传递的是这个地址的副本,所以是值传递。一个典型的验证场景是,在一个函数中传递这个引用,函数里面的操作是将其置空或指向另一个对象,在执行完这个函数后,再取这个引用所指向对象的值,如果是按引用传递,那么此时对象的值一定会变,会根据函数的操作相应变空或是不同的值,但实际测试中发现仍然是原来的值,证明不是按引用传递的。

41、Java中有没有指针的概念?

42、Java中,栈的大小通过什么参数来设置?

43、一个空Object对象的占多大空间?

44、对象引用类型分为哪几类?

答:

(1)强引用,常见的Object obj = new Object()方式创建的对象

(2)软引用:SoftReference sf = new SoftReference(obj);用于描述一些有用但非必需的对象,主要用于实现类似缓存的功能。在将要发生内存溢出之前列进回收范围

(3)弱引用:WeakReference wf = new WeakReference(obj);在下一次垃圾回收时回收,无论内存是否足够,主要用于监控对象是否已被垃圾回收

(4)虚引用:PhantomReference pf = new WeakReference(obj);每次垃圾回收均会被回收,主要用于检测对象是否已从内存删除,唯一目的是该对象被回收时收到一个系统通知。

45、讲一讲垃圾回收算法。

46、如何解决内存碎片的问题?

47、如何解决同时存在的对象创建和对象回收问题?

48、讲一讲内存分代及生命周期。

49、什么情况下触发垃圾回收?

50、如何选择合适的垃圾收集算法?

51、JVM中最大堆大小有没有限制?

52、堆大小通过什么参数设置?

53、JVM有哪三种垃圾回收器?

54、吞吐量优先选择什么垃圾回收器?响应时间优先呢?

55、如何进行JVM调优?有哪些方法?

56、如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决?

57、从分布式系统部署角度考虑,分哪几层?

58、如何解决业务层的数据访问问题?

59、为了解决数据库服务器的负担,如何做数据库的分布?

60、什么是著名的拜占庭将军问题?

61、为什么说TCP/IP协议是不可靠的?

62、讲讲CAP理念。

63、怎么理解强一致性、单调一致性和最终一致性?

64、分布式系统设计你会考虑哪些策略?

65、最常见的数据分布方式是什么?

66、谈一谈一致性哈希算法。

67、paxos是什么?

68、什么是Lease机制?

69、如何理解选主算法?

70、OSI有哪七层模型?TCP/IP是哪四层模型。

 

框架,类加载采用的什么方式?

双亲委派模型?

答:指类加载的层次关系,除了启动类加载器外,都应该有自己的父类加载器。具体过程指的是一个类加载器收到了类加载请求时,并不是优先自己去加载这个类,而是委派给自己的父类加载器去完成加载,只有在父类加载器无法完成这个加载请求时,才会尝试自己去加载,每一个层次的类加载器都遵守这个原则。

拓展1:什么时候无法完成类加载请求?

答:每个类加载器有自己的搜索范围,不在这个范围的就无法完成,比如启动类加载器负责的是放在\lib目录中的,或被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(按文件名识别,如rt.jar)类库加载到虚拟机内存中,扩展类加载器负责加载\lib\ext目录中的,或被java.ext.dirs系统变量所指定的路径中的所有类库。

拓展2:双亲委派模型的好处?(为什么要使用双亲委派模型?双亲委派解决了什么问题?)

答:Java类随着它的类加载器一起具备了一种带优先级的层次关系,能够防止基础类被随意顶替。比如java.lang.Object存放在rt.jar中,无论使用哪一个类加载器,最终都会委派给最顶端的启动类加载器,因而Object类在程序的各种类加载环境中都是同一个类。反之,如果不使用双亲委派模型,由各个类加载器自行加载,那么如果用户自实现一个Object类,并放在程序的ClassPath中,那么系统中可能会出现多个不同Object类,基础类的混乱将导致应用程序的混乱。总结,双亲委派解决了各个类加载器的基础类的统一的问题

拓展3:双亲委派的实现?

答:双亲委派的实现代码都集中在java.lang.ClassLoader的loadClass()中,逻辑是:1.首先查找类是否被加载过,2.若没有加载且父类加载器存在则使用父类加载器的loadClass()加载,3.若无父类加载器,则使用启动类加载器4.捕获异常,即若父类加载器抛出ClassNotFoundException,再由本身的类加载器的findClass()加载

拓展4:哪些场景打破了双亲委派?为什么?

答:Java中所有涉及SPI(Service Provider Interface)的加载动作基本上都打破了,比如JNDI、JDBC、JCE、JAXB和JBI等,双亲委派无法解决需要基础类调用用户代码的场景。这些技术由独立厂商实现并部署在应用程序的ClassPath,由启动类加载器去加载的时候,启动类加载器并不能识别,此时引入了一个线程上下文类加载器(Thread Context ClassLoader),JNDI服务使用线程上下文类加载器去加载所需的SPI代码,即达到父类加载器请求子类加载器去完成类加载的目的。

哪些框架软件打破了双亲委派?

热部署?

流控的原理?

Mybatis问题

1.#{}和${}的区别?

答:#{}是预编译处理,${}是字符串替换。Mybatis在处理#{}时,会将sql中的#{}替换为?,调用PreparedStatement的set方法来赋值,而处理${}时就是简单的替换值了,预编译的方式能够有效地防止SQL注入,提高心痛安全性。

2.通过一个XML的Mapper映射文件都会写一个Dao接口与之对应,那么Dao接口的工作原理是什么?Dao接口的方法,参数不同时,方法是否能够重载?

答:Dao接口即Mapper接口,通常Mapper的namespace值设为接口的全限定名,Mapper文件的MappedStatement的id值为接口的方法名,sql的传参即为接口方法的入参,通常Dao接口是没有实现类的。Dao接口的工作原理是JDK动态代理,Mybatis在运行时使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,并返回结果。

那么通过接口全限定名+方法名唯一定位一个与之对应的Mapper文件的id,Dao接口与Mapper文件的对应方式决定了Dao接口的方法不能重载。

3.Mybatis是如何进行分页的?分页插件的原理是什么?

答:Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

4.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

答:原理:定义列名与对象属性名之间的映射关系,有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的,Mybatis会忽略列名大小写。

(1)使用标签,逐一定义列名和对象属性名之间的映射关系;(2)使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis能智能找到与之对应对象属性名。

5.XML映射文件除了常见的select|insert|update|delete标签之外,还有那些标签?(注:出自京东面试官)

答:动态sql的9个标签trim|where|set|foreach|if|choose|when|otherwise|bind等,其中为sql片段标签,通过标签引入sql片段,为不支持自增的主键生成策略标签。

6.简述Mybatis的插件运行原理,以及如何编写一个插件

答:Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,除此之外,还需要在配置文件中配置你写的插件。

7.一级、二级缓存

答:(1)一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或close之后,该Session中的所有Cache就将清空。

(2)二级缓存与一级缓存机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并可自定义存储源,如Ehcache。开启二级缓存需要在SQL映射文件中添加

(3)对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)进行了C/U/D操作后,默认该作用域下所有select中的缓存将被清理。

可参考:https://blog.csdn.net/zh15732621679/article/details/78965789,讲的比较通俗易懂

8.Mybatis是否支持延迟加载?如果支持,他的实现原理是什么?

答:Mybatis仅支持associations关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

原理:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了。接着完成a.getB().getName()方法的调用。

9.Mybatis映射文件中,如果A标签通过include引用了B标签的内容,那么B标签能否定义在A标签之后?

答:可以。虽然Mybatis解析XML映射文件是按照顺序解析的,但是被引用的B标签是可以在任何地方定义的。

原理:Mybatis解析A标签,发现A引用的B标签尚不存在,此时Mybatis会将A标记为未解析状态,然后继续解析余下标签,包括B,当所有标签第一轮解析完毕后,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B已存在,则A标签可以正常完成解析。

拓展1:多层嵌套是否会导致解析速度慢?循环嵌套会如何处理?是否是有限的轮次?

10.简述Mybatis的XML映射文件和Mybatis内部数据结构之间的映射关系

答:Mybatis将所有XML配置信息都封装到ALL-In-One重量级对象Configuration内部。在XML映射文件中,标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。标签会被解析解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个