java面试问题记录

1.创建线程的方式

 继承Thead,实现Runnable或者Callable(有返回值)接口,通过线程池的方式创建。

常见有那些线程池的创建方式

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

自己提到了这几种线程池的创建方式都不安全,会发生内存溢出的问题。

解析链接:https://blog.csdn.net/qq_34707456/article/details/103066406

线程池的核心参数

ThreadPoolExecutor的构造方法了解一下这个类:上面四种创建线程池的方式最后都会调用new ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
}

构造参数比较多,一个一个说下:

    corePoolSize:线程池中的核心线程数;
    maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
    keepAliveTime:线程池中非核心线程闲置超时时长(准确来说应该是没有任务执行时的回收时间,后面会分析);
        一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
        如果设置allowCoreThreadTimeOut(boolean value),则也会作用于核心线程
    TimeUnit:时间单位。可选的单位有分钟(MINUTES),秒(SECONDS),毫秒(MILLISECONDS) 等;
    workQueue:任务的阻塞队列,缓存将要执行的Runnable任务,由各线程轮询该任务队列获取任务执行。可以选择以下几个阻塞队列。
        ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
        LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
        SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
        PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
    ThreadFactory:线程创建的工厂。可以进行一些属性设置,比如线程名,优先级等等,有默认实现。
    RejectedExecutionHandler:任务拒绝策略(饱和策略),当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
        AbortPolicy:直接抛出异常。
        CallerRunsPolicy:这提供了一个简单的反馈控制机制,可以减慢提交新任务的速度。
        DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务。
        DiscardPolicy:不处理,丢弃掉。
        当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
 

jdk1.8中新增了那种线程池,还可以怎么创建线程?

 新增线程池:newWorkStealingPool

适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中。

新增创建线程方式:使用lambda方式创建

new Thread(()-> {
            print.printNum();
        }).start();

2.ThreadLocal了解吗,谈下你的理解

java就是通过ThreadLocal来实现线程本地存储的,ThreadLocal就是每个线程自己的本地变量。

     

 static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        private Entry[] table;

可以看到ThreadLocalMap里面有个Entry数组,只有数组没有像HashMap那样有链表,因此当hash冲突的之后,ThreadLocalMap采用线性探测的方式解决hash冲突。

线性探测,就是先根据初始keyhashcode值确定元素在table数组中的位置,如果这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次直至找到能够存放的位置。在ThreadLocalMap步长是1。

用这种方式解决hash冲突的效率很低,因此要注意ThreadLocal的数量

而且这个EntryThreadLocal的弱引用作为key。那为什么要搞成弱引用(只要发生了GC弱引用对象就会被回收)呢?

首先ThreadLocal内部没有存储任何的值,它的作用只是当我们的ThreadLocalMap的key,让线程可以拿到对应的value。当我们不需要用这个key的时候我们,我们把fooLocal=null这样强引用就没了。假设Entry里面也是强引用的话,那等于这个ThreadLocal实例还有个强引用在,那么我们想让GC回收fooLocal就回收不了了。那可能有人想,你弄成弱引用不是很危险啊,万一GC一下不是没了?别怕只要fooLocal这个强引用在这个ThreadLocal实例就不会回收的。

因此弄成弱引用,主要是让没用的ThreadLocal得以GC清除。

这里可能还有人问那key清除掉了,value咋办,这个Entry还在的呀。是的,当在使用线程池的情况下,由于线程的生命周期很长,某些大对象的key被移除了之后,value一直存在的就可能会导致内存泄漏。

不过java考虑到这点了。当调用get()、set()方法时会去找到那个key被干掉的entry然后干掉它。并且提供了remove()方法。虽然get()、set()会清理keynull的Entry,但是不是每次调用就会清理的,只有当get时候直接hash没中,或者set时候也是直接hash没中,开始线性探测时候,碰到key为null的才会清理。

ThreadLocal本质就是避免共享,在使用中注意内存泄露问题和hash碰撞问题即可

3.volatile的作用

   保证线程间的可见性,禁止重排序

 volatile怎么保证线程间的可见性

  线程直接读写主内存中的变量,不去读缓存中的变量。

volatile可以保证线程间的原子性吗,为什么

    不能,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。

所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。
 

4.java的内存结构

  堆,虚拟机栈,本地方法栈,程序计数器,元空间。

  说下怎么判断对象是否存活

   引用计数法(无法解决对象相互依赖的问题)        根搜索算法

 说下常用的垃圾回收算法

  新生代 :复制算法                        老年代: 标记清除   标记整理

标记清除和标记整理的区别:

  标记清除是判断一个对象不可达后就删除这个对象,是一个一个的删除,这样会产生大量的内存碎片,影响程序的效率。

 标记整理会对不可达对象进行排序,把不可达对象放在一片连续的内存中,然后整体删除这片内存。

说下常用的垃圾回收器

cms   g1

cms那些阶段会发生STW

初始标记     重新标记

g1收集器为什么可以设定回收的时间

g1回收器中,将新生代,老年代都划分成了很多的小块内存,可以根据设定的回收时间来回收一定数量的内存快。

5.java中的类加载机制

  加载 链接  初始化   ,其中链接分为  验证  准备 解析

 提到类加载时采用双亲委派模型,如果父类加载器可以加载就尝试交给父类加载器加载,然后父类加载器在尝试交给父类的父类加载器加载(这样依次往上,直到根加载器为止),如果父类加载器不能加载,子类加载器才尝试加载,这样可以保证一个类只被加载一次。

6.orcale和mysql的分页

   mysql中通过使用limit  m ,n  来实现分页,表示查询  m+1--m+n+1行 ,为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:  limit m,-1

  orcale中使用rownum来实现分页:rownum不支持大于,等于号,只支持小于号,如果想要查询中间的数据需要使用子查询

select *  from (select rownum rm, a.* from (select * from table) a where rownum < 11) b where b.rm > 5

7.说下jdk1.8中的Stream流,怎么把array转化为流(直接回答不知道--说这个在工作中用的多)

8.说下rabbitmq的工作原理,用到了那些设计模式

谈了下rabbitmq的几种工作模式,主要谈了其中的路由模式,生产者将消息发给交换机,然后交换机根据路由键将 消息发给指定的消费队列。

用到了观察者模式,发布订阅模式。

9.说下令牌桶算法

这个主要是用来做服务的限流的,就是令牌桶中按一定的速率产生令牌(可以设置,m/s),服务请求的时候选取令牌桶中获取令牌,如果获取到就执行,在指定的时间没有获取到令牌就走服务的降级。

10.redis的持久化机制

       谈了下aof和rdb的区别,aof实时记录,rdb是m时间n个key发生变化才记录一次。

  

发布了68 篇原创文章 · 获赞 93 · 访问量 8490

猜你喜欢

转载自blog.csdn.net/qq_34707456/article/details/103819952