快递100一面(Java)

面试邀请来得有突然,记录一下有些问题以及答案。

ArrayList 是线程安全的吗,怎样保证他线程安全。

原因:
其实大部分集合类都不是线程安全的,其关键的原因在于添加元素的底层实现,因为在arrayList的add()中:

elementData[size++] = e;

这一步包括了自增和赋值,因为当线程A执行了赋值以后,暂停转而运行其他的线程,但是还没扩容,就会导致,B线程的值直接覆盖A的值,

解决:
调用Collections.synchronizedList方法 ,其原理也就是将ArrayList里的方法添加synchronized关键字,也就是加锁,也就是经典的装饰器模式

HashMap会出现什么问题,如何解决?

首先HashMap是一个高效的数据结构,可以在O(1)时间复杂度查询数据,原理大致为:将Key通过Hash算法得到一个地址值,再获取内存中想要的数据,但是Hash算法存在缺陷,就是不同的Key值通过计算得到的地址值又很小的几率相同,这就是Hash冲突。
解决方法:

  1. 开放寻址法:
    它的核心思想就是重新探测新的位置来解决冲突,如何寻找新的地址:
    1.线性探测法:
    当向Hash表中插入数据时,发现该地址已经有数据了,那就从这个位置开始在数组中往后查找,直到找到空闲位置为止;缺点是随着不断填充,空闲的位置变少,探测的时间就会增加,
    2.二次探测法:
    探测的下标为hash(key)+0² 、 hash(key)+1² …
    3.双重哈希法:
    使用多个hash函数计算key ,如果第一个hash函数计算出来的地址被占用了,就调用第二个函数 ,直到找到空闲位置为止;

  2. 链表法:
    该方法更常用,原理为:在hash表中每一个桶或者槽(slot) 都会对应一个链表,将hash值相同的元素放到相同槽位的对应链表中,插入数据的时候,只需要通过hash函数计算出对应的hash值,然后添加到链表之后。链表法性能取决于hash函数的性能,尽量保证链表长度足够小。

如何创建一个线程池,其中的参数如何设置?

  1. 通过Executors类提供的静态方法Executors.newCachedThreadPool()
    创建一个可缓存的线程池,若线程数超过所需,缓存一段时间后会回收,若线程数不够,则新建线程,初始状态下线程池没有线程,而线程不足会自动创建线程。
  2. 通过Executors类提供的静态方法Executors.newFixedThreadPool(3);
    创建一个固定大小的线程池
  3. 通过Executors类提供的静态方法Executors.newScheduledThreadPool(3);
    创建一个周期性的线程池,支持定时及周期性执行任务。
private static void createScheduledThreadPool() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        System.out.println(DateUtil.now() + " 提交任务");
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.schedule(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            }, 3, TimeUnit.SECONDS);
        }
    }

创建大小为3的线程池,设置定时时间为3秒

  1. 通过Executors类提供的静态方法Executors.newSingleThreadExecutor();
    创建只有一个线程的线程池
  2. 通过自定义ThreadPoolExecutor构造器(重要)
    ThreadPoolExecutor类提供了4种构造方法,可根据需要来自定义一个线程池。
    共有7个相关参数
    1. corePoolSize 核心线程数,线程池始终存活线程的数量
    2. maximumPoolSize 最大线程数,线程池中允许的最大线程数。keepAliveTime
    3. keepAliveTime 线程没有任务执行时最多保持多久时间会终止
    4. unit 时间单位
    5. workQueue 堵塞队列 有7中选择
    - ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列。
    - LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列
    - SynchronousQueue 一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
    - PriorityBlockingQueue 一个支持优先级排序的*阻塞队列。
    - DelayQueue 一个使用优先级队列实现的*阻塞队列,只有在延迟期满时才能从中提取元素。
    - LinkedTransferQueue 一个由链表结构组成的*阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
    - LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列
    6. threadFactory 线程工厂 主要用来创建线程
    7. handler 拒绝策略 拒绝处理任务时的策略 4种
    - AbortPolicy 直接拒绝 抛出异常
    - CallerRunsPolicy 重试 再次调用运行该任务的execute()
    - DiscardOldestPolicy 抛弃头部(最旧) 的任务,并执行当前任务
    - DiscardPolicy 抛弃当前任务

JVM虚拟就内存结构:
我把它分为两类:线程公有 线程私有
java堆和方法区是线程共有的
java栈、本地方法栈、PC寄存器是每个线程独有一份

  1. 方法区主要是保存类的信息以及各种静态变量和常量;
  2. java堆主要保存实例对象
  3. java栈是程序方法运行的结构,每个方法都单独存入栈中,各自存有局部变量表操作数栈、以及动态链接和方法出入口
  4. 本地方法栈是为以native关键字修饰的方法服务
  5. PC寄存器是记录下一条需要执行的字节码指令

待续

上一篇:输出100以内的素数


下一篇:读懂Swift 2.0中字符串设计思路的改变