java八股文日更【四】(2023-4-13)

114 阅读8分钟

一、ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
  • 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList 和 LinkedList 都不是线程安全的;

二、HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现

在Java中,保存数据有两种比较简单的数据结构:数组和链表。**数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;**所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做 拉链法 的方式可以解决哈希冲突。

扩容:当HashMap中的元素个数超过数组大小*负载因子(0.75)时,就会进行数组扩容。负载因子默认值为0.75,数组大小默认为16,扩容大小为原来的2倍,即当HashMap中元素个数超过12的时候,会触发扩容,大小变为32

不同点:

  1. JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法。因为使用头插法在多线程情况下扩容会出现死循环,出现环状链表,因为扩容时链表的顺序会变化
  2. JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构。当链表长度大于阈值(默认为8)时,将链表转化为红黑树,当链表长度低于6会从红黑树转化成链表。

三、Spring bean 的生命周期

1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。

2)利用依赖注入完成 Bean 中所有属性值的配置注入。

3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。

4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。

7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。

8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

10)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

四、Spring事务是怎么实现的?

  1. Spring事务底层是基于数据库事务和AOP机制的
  2. 首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean
  3. 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解
  4. 如果加了,那么则利用事务管理器创建一个数据库连接
  5. 并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交
  6. 然后执行当前方法,方法中会执行sql
  7. 执行完当前方法后,如果没有出现异常就直接提交事务
  8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务

五、Spring 如何解决循环依赖?

参考链接:解决循环依赖

  • 一级缓存:为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
  • 二级缓存:为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean;
  • 三级缓存:为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法。

六、谈谈你对 volatile 的理解?

volatile 是 Java 虚拟机提供的轻量级同步机制

有三大特点:

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

七、synchrized 和 lock 的区别?

  1. synchrized 是关键字,属于jvm层面;lock是java的一个类,是api层面的锁
  2. synchrized 不需要用户手动去释放锁;Reentranlock 需要用户手动释放锁,需要使用 lock()unlock()方法配合 try/finally 语句块来完成
  3. synchrized 不可中断,除非抛出异常或者正常执行完成;
  4. Reentranlock 设置超时方法 tryLock(long time, TimeUnit unit)
  5. lockInterruptibly() 放代码块中,调用interrupt()方法 可以中断
  6. synchrized 非公平锁;Reentranlock 两者都可以,默认非公平锁,构造方法可以传入Boolean 值,true为公平锁,false 为非公平锁
  7. Reentranlock 可以绑定多个条件 condition,可以精确唤醒。

八、创建线程的三种方式

  1. 继承 Thread类,重写 run 方法

  2. 实现 Runnable接口,实现run方法

  3. 实现 Callable 接口,实现 call 方法,该类作为构造参数实例化 FutureTask,将实例化的 FutureTask传入 Thread的参数,调用 start 方法启动

    /**
     * 创建线程方式--使用FutureTask方式 
     */
    public class FutureTasktest {
        public static void main(String[] args) {
            CallerTask callerTask = new CallerTask();
            //创建异步任务
            FutureTask<String> futureTask = new FutureTask<>(callerTask);
            //启动线程
            new Thread(futureTask).start();
            String result;
            try {
                //等待任务执行完毕,并返回结果。
                result = futureTask.get();
                System.out.println("result:"+ result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    ​
        /**
         * 创建任务类,类似Runable
         */
        public static class CallerTask implements Callable<String>{
    ​
            @Override
            public String call() throws Exception {
                return "hello";
            }
        }
    }
    

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

  1. ArrayBlockingQueue

    是一个用数组结构实现的有界阻塞队列,此队列按FIFO(先进先出)排序元素

  2. LinkedBlockingQueue

    一个基于链表结构阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool使用了这个队列

  3. SynchronousQueue

    一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool使用了这个序列

  4. PriorityBlockingQueue

    一个具有优先级的无限阻塞队列

十、线程的参数及常见的几种线程池

# 源码
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. 参数一:corePoolSize 线程池中的常驻核心线程数
  2. 参数二:maximumPoolSize 线程池最大同时执行的线程数
  3. 参数三:keepAliveTime,多余的空闲线程存活时间,当空闲时间达到 keepAliveTime 值时,多余的线程会被销毁,只剩下corePoolSize 个线程
  4. 参数四:unit,keepAliveTime的单位
  5. 参数五:workQueue,任务队列,被提交但尚未执行的任务
  6. 参数六:threadFactory,表示生成线程池的工作线程的线程工厂,用户创建新线程,一般使用默认即可
  7. 参数七:handler,拒绝策略,表示当前线程队列满了并且工作线程大于线程池的最大数量maximumPoolSize 时,该如何处理

四种线程池拒绝策略:

  • AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常
  • CallerRunsPolicy:由调用线程处理该任务
  • DiscardPolicy:丢弃任务,但是不抛出异常
  • DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务

几种常见的线程池:

  1. newSingleThreadExecutor

    主要特征如下:创建一个单线程的线程池,它会用唯一的工作线程来执行任务,保证所有任务都能按照顺序执行

  2. newFixedThreadPool

    1. 创建一个定长的线程池,可控制线程的最大并发数,超出的线程会在队列中等待
    2. 使用的是LinkedBlockingQueue
  3. newCachedThreadPool

    1. 创建一个可缓存的线程池,如果线程池超过处理需要,则灵活回收空闲线程,若无可回收,则创建新线程
    2. 它将corePoolSize 设置为了0,将maxnumPoolSize设置为了 Integer.MAX_VALUE,他使用的是SynchronousQueue,如果线程空闲超过60秒,就回收线程
  4. newScheduleThreadPool

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

线程池优点

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性
  • 提供定时执行、定期执行、单线程、并发数控制等功能