Java面试必问知识点之一

·  阅读 38

HashSet如何保证不重复的数据

HashSet构造函数
public HashSet() {
        map = new HashMap<>();
    }
// 还是通过HashMap来实例化一个map
复制代码

add()

  public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
//它将传入的对象当做HashMap的key,由于hashMap的key是不允许重复的,所以每当重复的值传入HashSet时,value会被覆盖,而key不会有影响,就样就保证了HashSet中没有重复的数据
复制代码

LinkedHashMap如何保证数据的有序性

HashMap是一个无序的map,基于有排序场景的需求,JDK推出一个底层继承于HashMap实现,由一个双向链表所构成。

排序方式:

  • 根据写入的顺序
  • 根据访问的顺序
   /**
     * The head of the doubly linked list.
     */
    private transient Entry<K,V> header;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    private final boolean accessOrder;
    
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
    } 
复制代码

synchronized 关键字原理

synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式:

  • 同步普通方法,锁的是当前对象。
  • 同步静态方法,锁的是当前 Class 对象。
  • 同步块,锁的是 () 中的对象。

实现原理

JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。

其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。

而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

多线程常见问题

上下文切换

多线程并不一定是要在多核处理器才支持的,就算是单核也是可以支持多线程的。 CPU 通过给每个线程分配一定的时间片,由于时间非常短通常是几十毫秒,所以 CPU 可以不停的切换线程执行任务从而达到了多线程的效果。

但是由于在线程切换的时候需要保存本次执行的信息(详见),在该线程被 CPU 剥夺时间片后又再次运行恢复上次所保存的信息的过程就称为上下文切换。

上下文切换是非常耗效率的。

通常有以下解决方案:

  • 采用无锁编程,比如将数据按照 Hash(id) 进行取模分段,每个线程处理各自分段的数据,从而避免使用锁。
  • 采用 CAS(compare and swap) 算法,如 Atomic 包就是采用 CAS 算法(详见)。
  • 合理的创建线程,避免创建了一些线程但其中大部分都是处于 waiting 状态,因为每当从 waiting 状态切换到 running 状态都是一次上下文切换。

死锁

死锁的场景一般是:线程 A 和线程 B 都在互相等待对方释放锁,或者是其中某个线程在释放锁的时候出现异常如死循环之类的。这时就会导致系统不可用。

常用的解决方案如下:

  • 尽量一个线程只获取一个锁。
  • 一个线程只占用一个资源。
  • 尝试使用定时锁,至少能保证锁最终会被释放。

资源限制

当在带宽有限的情况下一个线程下载某个资源需要 1M/S,当开 10 个线程时速度并不会乘 10 倍,反而还会增加时间,毕竟上下文切换比较耗时。

如果是受限于资源的话可以采用集群来处理任务,不同的机器来处理不同的数据,就类似于开始提到的无锁编程。

文章有帮助你,请关注微信公众号:肆意游离 有更多精彩等着你

分类:
后端
标签:
分类:
后端
标签: