0%

Java 基础知识

bing

Java基础

  1. JDK和JRE的区别?
    JDK全称Java Development Kit,包含Java运行环境(JRE)和开发环境,面向Java开发者,提供了一系列开发工具,比如 javac,需要配置环境变量;
    JRE全称Java Runtime Enviroment,是Java运行环境,面向Java使用者,不需要配置环境变量;

  2. equals()== 的区别?
    == 比较的是引用,即在内存空间中的存储位置是否一致;
    equals()Object 类提供的方法,可以由子类重写,如何比较由开发者决定,一般比较的是内容;

    1
    2
    3
    public boolean equals(Object obj){
    return (this == obj);
    }

    Object 类中的 equals() 方法的默认实现和 == 等价,Java 中很多类都对 equals() 方法进行了重写,比如 String

  3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

    理论上要求:

    equals() 返回truehashCode() 返回值相等;
    hashcode() 返回值相等,equals() 不一定返回true

    实际上:

    equals()返回值相等,hashCode()返回值不一定相等;

    equals() 重写后,还要重写hashcode()hashCode() 能提高哈希表的性能;

  4. final finally finalize 的区别?

    final:

    修饰类:该类不能被继承,final类中所有的成员方法都会隐式的定义为final方法;

    修饰方法:该方法不能被重写;

    修饰变量:常量,只能赋值一次,不能更改;

    finally

    1
    2
    3
    4
    5
    6
    7
    try {
    ...
    }catch(Exception e){
    ...
    }finally {
    ...
    }

    finally经常被用作释放资源;

    finalize()

    这个方法在gc启动,该对象被回收的时候被调用;

  5. Java基本数据类型?
    byte short int long double float char boolean

    DoubleFloat 包装类没有常量池;

  6. String StringBuffer StringBuilder 的区别?
    String:字符串常量,线程安全;

    StringBuffer:字符串变量,线程安全;

    StringBuilder:字符串变量,线程不安全;

  7. 抽象类和接口的区别?

    • 默认方法实现:抽象类可以有默认方法,接口不可以(Java 8 可以)
    • 实现:继承抽象类使用extends,实现接口使用implements
    • 构造方法:抽象类有构造方法,接口没有构造方法
    • 访问修饰符:抽象类有public、protected、default、private,接口只有public
    • main方法:抽象类有main方法,接口没有main方法
    • 添加新方法:抽象类添加新方法不需要改变子类方法,接口需要改变子类
  8. switch 接受数据类型

    byte short int char String enum

    不支持 boolean

  9. 空接口的作用
    标志着Java类实现了某个特性或功能

  10. Java 泛型
    类型参数化,将类型转换的类型检查从运行时提前到编译时。省去了强制类型转换,提高安全性。

  11. 自动装箱和自动拆箱

    • 自动装箱:静态方法 Integer.valueOf()
    • 自动拆箱:实例方法 i.intValue()
  12. 异常

    • 可检查异常:编译期间能检查到的异常,需要在代码中进行捕获或抛出异常,比如 IOException
    • 运行时异常:只有在运行期才能知道的异常,比如空指针异常,数组越界
    • Error:会导致程序处于不正常状态,不需要捕获,比如 OutOfMemoryError
  13. foreach 与 for

    • for:遍历数组结构的数据,采用 for 更优
    • foreach:遍历链表结构的数据,采用 foreach 更优
  14. 静态分派和动态分派

    Java静态分派与动态分派

Java容器

集合知识点

gif

  1. ArrayList

    • 默认大小:10
    • 扩容:原容量的1.5倍,$oldCapacity + (oldCapacity >> 1)$,使用Arrays.copyOf() 复制数组
    • 删除元素:使用System.copyOf()​ 复制数组
    • modCount:记录 ArrayList 结构发生变化的次数。ArrayList使用size维护自身状态,Iterator使用cursor维护自身状态,不同步
  2. Vector

    • 默认大小:10

    • 扩容:原容量的2倍

  3. CopyOnWriteArrayList

    读写分离,写时复制,在写时允许读操作,适用于读多写少的场景;读不加锁,写加锁;不适合内存敏感以及对实时性要求很高的场景;

  4. LinkedList

    • 默认大小:0
  5. ArrayListLinkedList的比较

    • ArrayList使用动态数组,LinkedList使用双向链表
    • ArrayList支持随机访问,LinkedList不支持
    • ArrayList插入删除耗时长,LinkedList能快速插入删除
  6. HasMap

  7. fail-fast
    fail-fast 机制是java集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

  8. HashTable

  9. LinkedHashMap

    • 保存了记录的插入顺序,遍历时是有序的
  10. TreeMap

    • 能够根据键排序,默认按照键的升序排序,遍历时是排好序的
  11. ConcurrentHashMap

    • 分段锁,初始并发 16
    • 扩容:segment 内扩容
    • 迭代器是弱一致性:如果已经遍历完的数据发生了变化,不会抛异常;未遍历到的数据发生了遍历,会遍历到新的数据

Java 多线程

线程池使用不当会造成内存溢出
Java Concurrency代码实例之一执行者与线程池
多线程面试题(一)
多线程面试题(二)

Java 提供了 JUC 包,包括:

执行者和线程池

  1. 执行者:Executor 根接口、ExecutorService接口、Executors 工具类
  2. 线程池:ThreadPollExecutor 线程池、Executors.newCachedThreadPool() 线程池、Executors.newFixedThreadPool(int) 线程池、Executors.newSingleThreadPool(int) 线程池
  3. 异步值:Future 接口、FutureTask
  4. ForkJoinForkJoinPoolRecursiveActionRecursiveTask

阻塞队列

  1. LinkedBlockingQueue:链表实现,不可实现公平策略
  2. ArrayedBlockingQueue:数组实现,实现公平策略
  3. SynchronousQueue:支持公平策略
  4. PriorityBlockingQueue
  5. DelayQueue
  6. BlockingDeque
  7. ConcurrentLinkedQueue
  8. ConcurrentLinkedDeque

同步工具

  1. CountdownLatch

  2. Semaphore

  3. CyclicBarrier

  4. Exchange

并发集合

  1. ConcurrentHashMap
  2. ConcurrentSkipListMap
  3. CopyOnWriteArrayList

  1. LockSupport:能够实现线程的阻塞和唤醒,不需要放在同步代码块中;unpark() 可以优先于park() 调用

  2. AQS:一个状态值state、阻塞和唤醒线程的能力LockSupport、阻塞队列

  3. RentrantLock

  4. Condition

  5. ReentrantWriteReadLock

原子变量

  1. AtomicInteger
  2. AtomicLong
  3. AtomicBoolean
  4. AtomicReference
  5. AtomicStampedReference
  6. AtomicMarkableReference

synchronized volatile

  • synchronized:保证原子性和可见性,不会限制指令重排

  • volatile:保证可见性和有序性,无法保证原子性。Volatile 可用于提供线程安全,但具有使用条件:多个变量之间或某个变量的当前值与修改后的值没有约束。Volatile 用于多线程环境下的单次操作(单次读或单次写)。Volatile 的写操作不能依赖于当前值,读操作不能依赖于其他变量

    volatile的适用场景

    • 状态标记
    • 一次性安全发布
    • volatile bean 模式
    • 开销较低的“读写锁”

双重检查单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private volatile static Singleton instance;
private Singletin(){}

public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
}
return instance;
}
}
  • synchronized:保证执行原子性
  • volatile:保证有序性

如果不加 volatile 无法保证有序性,instance = new Singleton() 可能出现先创建引用再创建对象的情况,此时会出现一个问题:instance 不为 null ,但对象并没有创建万;使用 volatile ,可以保证有序性,保证先创建对象再进行引用赋值

Java 线程状态转换

  • 创建
  • 就绪:Thread.start() 会创建线程,并进入就绪状态等待运行
  • 运行
  • 阻塞
    • 同步阻塞:synchronized
    • 限期等待:Thread.sleep()
    • 无限期等待:Obiect.wait()
  • 终止

wait sleep yield

  • wait():Object 实例方法,线程会释放锁,线程状态由运行状态变为无限期等待状态
  • sleep():Thread 静态方法,线程不会释放锁,线程由运行状态变为限期等待状态
  • yield():Thread 静态方法,线程不会释放锁,线程由运行状态变为就绪状态

多线程优点

  • 提高应用程序并发度和吞吐量,提高 CPU 利用率
  • 能够充分利用多核 CPU 或多个 CPU
  • 线程不仅有 CPU 计算时间,还有 IO 读取时间等。对于单核 CPU 来说,当一个线程等待 IO 到达而被阻塞时,可以执行其他线程,从而提高 CPU 利用率

ThreadPool

  • Executors.newCachedThreadPool()

    1
    2
    3
    4
    5
    6

    public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>());
    }

    核心线程数为 0,最大线程数为 Integer.MAX_VALUE,空闲线程存活时间为 60s ,当线程数超过核心线程数时使用同步队列保存任务。

    由于使用同步队列,如果想将一个元素放入SynchronousQueue,必须至少一个在等待接受这个元素。所以对于缓存线程池,来一个任务,如果有空闲线程会将任务交由空闲线程处理;如果没有空闲线程,再创建一个新的线程处理。

    SynchronousQueue的最大线程数是Integer.MAX_VALUE,对于高并发任务,同时创建多个线程很有可能造成内存溢出甚至宕机。

  • Executors.newFixedThreadPool(int)

    1
    2
    3
    4
    5
    public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
    }

    核心线程数和最大线程数都是 nThreads,当任务数超过线程数时使用LinkedBlockingQueue。由于LinkedBlockingQueue最大值是Integer.MAX_VALUE,当高并发场景很可能队列太大导致内存溢出。

  • Executors.newSingleThreadExecutor()

    1
    2
    3
    4
    5
    public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
    }

    同上,单线程能保证任务有序执行。也会存在内存溢出的风险。

由于以上三种线程池在高并发任务中都存在内存溢出的风险,可以使用自己实现的线程池。选择有界队列如ArrayBlockingQueue,并且采取适当的饱和策略,JDK 提供了 4 种饱和策略:

  • 终止策略:抛异常
  • 抛弃策略:抛弃任务
  • 抛弃旧任务策略:抛弃任务
  • 调用者策略:不抛异常也不抛弃任务,而是将任务交由主线程执行

LinkedBlockingQueue 也是有界队列,默认值为 Integer.MAX_VALUE,也可以自己设定值

submit execute

  • submit 提交 callable 方法,有返回值
  • execute 提交 runnable 方法,无返回值

ThreadLocal

Java并发编程:深入剖析ThreadLocal

ThreadLocal内存泄漏真因探究

每个 Thread 都有 threadlocals 成员变量,其类型是ThreadLocal.ThreadLocalMap

1
ThreadLocal.ThreadLocalMap threadLocals = null;

当调用 ThreadLocal 类的 get()set() 方法时,实际上是拿到 thread 类的 threadLocals 变量。

ThreadLocal 的应用场景:

  • 数据库连接
  • Session 管理
  • Web 请求