一、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
不同点:
- JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法。因为使用头插法在多线程情况下扩容会出现死循环,出现环状链表,因为扩容时链表的顺序会变化
- 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事务是怎么实现的?
- Spring事务底层是基于数据库事务和AOP机制的
- 首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean
- 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解
- 如果加了,那么则利用事务管理器创建一个数据库连接
- 并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交
- 然后执行当前方法,方法中会执行sql
- 执行完当前方法后,如果没有出现异常就直接提交事务
- 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
五、Spring 如何解决循环依赖?
参考链接:解决循环依赖
- 一级缓存:为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
- 二级缓存:为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean;
- 三级缓存:为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法。
六、谈谈你对 volatile 的理解?
volatile 是 Java 虚拟机提供的轻量级同步机制
有三大特点:
- 保证可见性
- 不保证原子性
- 禁止指令重排
七、synchrized 和 lock 的区别?
synchrized
是关键字,属于jvm层面;lock是java的一个类,是api层面的锁synchrized
不需要用户手动去释放锁;Reentranlock
需要用户手动释放锁,需要使用lock()
和unlock()
方法配合try/finally
语句块来完成synchrized
不可中断,除非抛出异常或者正常执行完成;Reentranlock
设置超时方法tryLock(long time, TimeUnit unit)
lockInterruptibly()
放代码块中,调用interrupt()
方法 可以中断synchrized
非公平锁;Reentranlock
两者都可以,默认非公平锁,构造方法可以传入Boolean 值,true为公平锁,false 为非公平锁Reentranlock
可以绑定多个条件 condition,可以精确唤醒。
八、创建线程的三种方式
-
继承
Thread
类,重写 run 方法 -
实现
Runnable
接口,实现run方法 -
实现
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"; } } }
九、线程池都有哪几种工作队列?
-
ArrayBlockingQueue
是一个用数组结构实现的有界阻塞队列,此队列按FIFO(先进先出)排序元素
-
LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法
Executors.newFixedThreadPool
使用了这个队列 -
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法
Executors.newCachedThreadPool
使用了这个序列 -
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;
}
- 参数一:corePoolSize 线程池中的常驻核心线程数
- 参数二:maximumPoolSize 线程池最大同时执行的线程数
- 参数三:keepAliveTime,多余的空闲线程存活时间,当空闲时间达到 keepAliveTime 值时,多余的线程会被销毁,只剩下corePoolSize 个线程
- 参数四:unit,keepAliveTime的单位
- 参数五:workQueue,任务队列,被提交但尚未执行的任务
- 参数六:threadFactory,表示生成线程池的工作线程的线程工厂,用户创建新线程,一般使用默认即可
- 参数七:handler,拒绝策略,表示当前线程队列满了并且工作线程大于线程池的最大数量maximumPoolSize 时,该如何处理
四种线程池拒绝策略:
- AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常
- CallerRunsPolicy:由调用线程处理该任务
- DiscardPolicy:丢弃任务,但是不抛出异常
- DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务
几种常见的线程池:
-
newSingleThreadExecutor
主要特征如下:创建一个单线程的线程池,它会用唯一的工作线程来执行任务,保证所有任务都能按照顺序执行
-
newFixedThreadPool
- 创建一个定长的线程池,可控制线程的最大并发数,超出的线程会在队列中等待
- 使用的是LinkedBlockingQueue
-
newCachedThreadPool
- 创建一个可缓存的线程池,如果线程池超过处理需要,则灵活回收空闲线程,若无可回收,则创建新线程
- 它将
corePoolSize
设置为了0,将maxnumPoolSize
设置为了Integer.MAX_VALUE
,他使用的是SynchronousQueue,如果线程空闲超过60秒,就回收线程
-
newScheduleThreadPool
创建一个定长线程池,支持定时及周期性任务执行
线程池优点:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
- 提供定时执行、定期执行、单线程、并发数控制等功能