# spring-cloud-study **Repository Path**: asdtiang_admin/spring-cloud-study ## Basic Information - **Project Name**: spring-cloud-study - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2017-06-09 - **Last Updated**: 2025-04-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README = java面试总结 abel :doctype: book :toc: left :page-layout: docs :toclevels: 4 :toc-title: 目录 :numbered: Doc Writer v1.0, 2018-09-28 = java面试总结 == java基础 java基础主要以集合,锁,线程池,Nio,版本特性为主要面试点。以下都以java 8来进行说明 === java集合架构 https://docs.oracle.com/javase/8/docs/technotes/guides/collections/reference.html[jdk8集合官方文档] 数据结构主要有List,Set,Map,队列,堆栈 ==== ArrayList 默认初始化容量10,扩容时会触发数组copy,带容量初始化会马上分配数组,不带时,会在第一次添加元素时,创建数组。 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } ---- 不加容量时初始化时数组为空,当add时才会触发扩容,但给定了初始容量时,数组会直接初始化。 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; ---- 扩容时操作 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } ---- Arrays.copyof调用的最终方法 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- public static T[] copyOf(U[] original, int newLength, Class newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } ---- ==== HashMap 主要问题是底层存储问题,这也是官方造成的,更改底层,然后很多面试都喜欢问了,都采用数组的方式存储数据 ,区别时jdk6和jdk7出现hash冲突的解决方式不一样,jdk6采用的LinkedList,jdk7采用红黑树。 官方原话:The alternative hash function improves the performance of these map implementations when a large number of key hash collisions are encountered. 话说这玩意其实意义不大,首先出现冲突的概念本来一般来说会很少,就算出现了,也不会说是特别大。那么问题来了,这jdk实现时到底多少才算是大呢 首先有2个容量一个转换: * HashMap本身的容量 * HashMap出现冲突时LinkedList容量 转换指LinkedList达到多少时进行Tree转换,减小到多少时再转换成LinkedList; 这几个概念搞清楚后看源码就比较清楚了。 先分析一下HashMap的源码里面定义的变量; [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** * The default initial capacity - MUST be a power of two. HashMap初始化容量默认值 16 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. 扩容默认因子 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64; /** * The next size value at which to resize (capacity * load factor). * * @serial */ // (The javadoc description is true upon serialization. // Additionally, if the table array has not been allocated, this // field holds the initial array capacity, or zero signifying // DEFAULT_INITIAL_CAPACITY.) int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor; /** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node[] table; /** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). */ transient Set> entrySet; static class Node implements Map.Entry { final int hash; final K key; V value; Node next; //省略 } /** * Constructs an empty HashMap with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } /** * Constructs an empty HashMap with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * Returns a power of two size for the given target capacity. //找到大于给定容量最近的2次幂,比如100,结果为128,500为512 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } ---- 给定初始化容量时,会计算容量值 tableSizeFor(int cap) ,这个容量值存储为 threshold ,但这里并没有初始化HashMap的存储数组, Node数组在第一次put时,会调用resize()进行相关操作,注意,没有给定初始化容量时,没有计算threshold。 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** 这个方法做的事情比较多,不容易看懂,主要做三个事, 第一是给定初始化容量时,计算threshold并初始化 第二是没有给定初始化容量时,用默认值计算threshold 第三个是HashMap不为空时,进行扩容,扩展的容量为原来的两倍(4->8,8->16)。触发为当前的length大于loadFactor* threshold 同时这个threshold值会为扩容的数组长度*loadFactor。 **/ final Node[] resize() { Node[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { ///已经初始化过,进行扩容操作 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //以前容量的2倍 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //如果用户给定了初始化容量,直接采用 // @1 else { // zero initial threshold signifies using defaults //没有给定容量时的初始化。第一次put时用默认值进行初始化 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { //这里代码太绕了,执行的逻辑为 //当用用户给定初始化容量时,计算出下次扩容值threshold,但先用newThr存储一下 //此部分代码可以放到 @1处 float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; //申请新的数组 @SuppressWarnings({"rawtypes","unchecked"}) Node[] newTab = (Node[])new Node[newCap]; table = newTab; if (oldTab != null) { //将原来的数组copy一份,因此要尽量给定初始值,减少copy for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode)e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } ---- 接下来就是何时转换TreeNode [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); else { //出现冲突 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //冲突达到一定数量后执行treeifyBin //TREEIFY_THRESHOLD为8 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //省略 ---- 然后就是treeifyBin方法 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- /** * Replaces all linked nodes in bin at index for given hash unless * table is too small, in which case resizes instead. 小于64时,执行 resize(),大于64时,冲突转换为tree。 */ final void treeifyBin(Node[] tab, int hash) { int n, index; Node e; //MIN_TREEIFY_CAPACITY 值为64 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode hd = null, tl = null; do { TreeNode p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } } ---- 总结,默认加载因子为0.75,初始化容量为16,数组长度*loadFactor为扩容时的判断条件值threshold, 扩容值为当HashMap的size达到threshold时进行扩容,扩容操作会导致整个数组copy操作。 当HashMap冲突每次达到7时,table的容量小于64会触发resize(),table的容量大于64时,会触发LinkedList向红黑树的转换 === java并发和锁 ==== 锁 首先是悲观锁和乐观锁的概念,主要思想是假设数据是否更改,悲观锁每次都会认为数据会被更改,因此在每次访问前都加一个锁,乐观锁认为每次数据都没变化,采用比较更新的策略。 java里面对应关系:synchronized(悲观锁),cas(比较交换乐观锁) java.util.concurrent 包下面Concurrent相关实现都是基于cas,cas底层调用Unsafe类,Unsafe调用本地方法实现 主要是有以下三个方法: * compareAndSwapInt * compareAndSwapLong * compareAndSwapObject 并发有以下几个包 * java.util.concurrent * java.util.concurrent.automic * java.util.concurrent.locks 锁资源,资源为单一资源或者多个资源 单一资源相关接口和类 * java.util.concurrent.locks.Lock 锁接口 * java.util.concurrent.locks.ReadWriteLock 读写锁接口 * java.util.concurrent.locks.ReentrantLock 可重入锁,可以给变量标识是否为公平锁,默认为非公平锁 * java.util.concurrent.locks.ReentrantReadWriteLock 可重入读写锁,可以给变量标识是否为公平锁,默认为非公平锁 * java.util.concurrent.locks.StampedLock 经过优化后读写锁,提供乐观锁的相关实现 * java.util.concurrent.locks.Condition 细粒度控制锁,提供await(),signal()等 多资源相关接口和类 * java.util.concurrent.CountDownLatch 多个资源达到计数器后,执行相关操作,计数器不可重置 * java.util.concurrent.CyclicBarrier 多个资源达到计算器后,执行相关操作,计数器可重置 * java.util.concurrent.Semaphore 资源给定数量,即可以指定可以同时有多少个线程可以访问资源,acquire(),release() 具体可以参考 http://tool.oschina.net/apidocs/apidoc?api=jdk-zh[api文档] ==== 队列 主要有队列,双端队列的相关实现,实现生产者消费者模型,主要类如下 * java.util.concurrent.BlockingQueue 阻塞队列接口,继承自接口 java.util.Queue * java.util.concurrent.BlockingDeque 阻塞双端队列接口,继承自接口 java.util.BlockingQueue * java.util.concurrent.ArrayBlockingQueue 阻塞队列数组实现,实现接口 java.util.BlockingQueue * java.util.concurrent.LinkedBlockingQueue 阻塞队列链接实现,实现接口 java.util.BlockingQueue 有了锁的机制,实现队列的生产阻塞,消费阻塞就很好实现了,源码上也是采用的相关锁机制,下面以ArrayBlockingQueue为例进行说明 [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- public class ArrayBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable { /** * Serialization ID. This class relies on default serialization * even for the items array, which is default-serialized, even if * it is empty. Otherwise it could not be declared final, which is * necessary here. */ private static final long serialVersionUID = -817911632652898426L; /** The queued items */ final Object[] items; /** items index for next take, poll, peek or remove */ int takeIndex; /** items index for next put, offer, or add */ int putIndex; /** Number of elements in the queue */ int count; /* * Concurrency control uses the classic two-condition algorithm * found in any textbook. */ /** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and default access policy. * * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity < 1} */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and the specified access policy. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @throws IllegalArgumentException if {@code capacity < 1} */ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity, the specified access policy and initially containing the * elements of the given collection, * added in traversal order of the collection's iterator. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @param c the collection of elements to initially contain * @throws IllegalArgumentException if {@code capacity} is less than * {@code c.size()}, or less than 1. * @throws NullPointerException if the specified collection or any * of its elements are null */ public ArrayBlockingQueue(int capacity, boolean fair, Collection c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion try { int i = 0; try { for (E e : c) { checkNotNull(e); items[i++] = e; } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i; } finally { lock.unlock(); } } //省略 /** * Inserts the specified element at the tail of this queue, waiting * for space to become available if the queue is full. * 放资源,如果没有位置了,等待 * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } //拿走资源,如果没有资源可用,则等待资源 public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } /** * Inserts element at current put position, advances, and signals. * Call only when holding lock. 生产者生产资源入队列,并通知等待的消费者消费 */ private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } /** * Extracts element at current take position, advances, and signals. * Call only when holding lock. 消费者消费资源,出队列,并通知等待的生产者放资源 */ private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x; } ---- 可以看到,三个构造函数,都会初始化锁,锁实现为ReentrantLock,并且可以指定是否为公平锁,然后在锁的基础上创建了两个监视器Condition 实现等待和唤醒的操作。 LinkedBlockingQueue 是基于链表实现的队列,可以指定链表的长度,默认为无界队列(实际上采用Integer.MAX_VALUE 2^31^-1). === java线程 ==== 线程的创建 1. 继承Thread类创建线程 2. 实现Runnable接口创建线程 3. 用Callable和Future创建线程 启动方式主要有,new Thread()后调用start(),或者提交给线程池。 ==== 线程的配置 主要配置线程堆栈的大小,此大小分配的内存为系统内存,不受jvm Heap影响,此配置默认值为 1M,变大之后,可创建的线程数最大值变小 ,变小之后,可创建的线程数最大值变大,一般可以达到5000没有问题。用-XX:ThreadStackSize=512 进行调整,单位默认为KB。 === java线程池 主要相关接口和类: * java.util.concurrent.Executor 线程池接口 * java.util.concurrent.ExecutorService 提供线程池相关接口方法 * java.util.concurrent.Executors 返回线程池相关实现类的实例工厂类 * java.util.concurrent.ThreadPoolExecutor 线程池主要看jdk文档即可理解,ThreadPoolExecutor 文档摘录如下: ---- 一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导: 核心和最大池大小 ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。 按需构造 默认情况下,即使核心线程最初只是在新任务到达时才创建和启动的,也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。如果构造带有非空队列的池,则可能希望预先启动线程。 创建新线程 使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。 保持活动时间 如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0, allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。 排队 所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互: 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 排队有三种通用策略: 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 被拒绝的任务 当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被 拒绝。在以上两种情况下, execute 方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略: 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。 定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。 钩子 (hook) 方法 此类提供 protected 可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。 如果钩子 (hook) 或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。 队列维护 方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。 remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。 终止 程序 AND 不再引用的池没有剩余线程会自动 shutdown。如果希望确保回收取消引用的池(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止:设置适当保持活动时间,使用 0 核心线程的下边界和/或设置 allowCoreThreadTimeOut(boolean)。 ---- === java 7 java 8 feature === java nio === java 类加载机制 == jvm === jvm 内存模式 image::./images/memory.png[] === jvm 常用调试命令 === 垃圾回收算法 常用的垃圾回收算法有 ==== 引用计数器 每一个对象都会有一个引计数器,当这个值为0时,可以进行回收,但问题是不好判断,循环引用的问题,即形成了孤岛。 ==== 根搜索算法 创建一个根节点“GC ROOT”,对象创建时都会挂到相关的节点上,因此当有路径到达节点时,节点处于使用状态,剩下没有路径从“GC ROOT”到达对象时 可以回收。 可以做GC ROOT的对象有以下几类 在java语言中,可作为GC Root的对象包括以下几种对象: 1. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。 2. 方法区中的类静态属性引用的对象。 3. 方法区中的常量引用的对象。 4. 本地方法栈中JNI本地方法的引用对象。 ==== java常用的收集算法 ===== 标记-清除 字面意思就很好理解,标记使用或者没有使用的对象,最后进行清除操作。缺点明显,会产生内存碎片 ===== 复制算法 将内存分为两块区域,扫描其中一块区域,对存活的对象复制到另一个块区域,然后清空当前区域,下次扫描另一块,进行一个交替操作。 优点很明显,速度快,不会产生内存碎片,缺点是可使用的内存相对变少。 ===== 标记整理算法 标记整理的第一阶段和标记清除一样,对对象进行标记是否可用,下一阶段进行整理,即删除回收对象,活动对象前移。 不会产生内存碎片,效率低 ===== 分代收集算法 这玩意就是新生代和老生代的那个事,多数对象存活时间不长,放在新生代中,新生代又分为Eden区,Survivor区,Survivor区 均分为Survivor From区,Survivor to区,也可说有两个Survivor区,老生代为常说的Old区。 流程大概是这样的,新生代,老生代,名字上就好理解,新产生的对象放新生代,存活久的放老年代, 这个主要有几个配置, === 垃圾回收器 === 代理 == 设计模式 === 设计原则 === 常用设计模式 ==== singleton ==== 工厂 ==== 观察者 ==== 策略模式 ==== 装饰者 ==== 其它 == spring === ioc aop === spring 事务 === spring boot ==== spring boot 加载机制 ==== spring boot starter 自定义 ==== spring boot 启动方式 ==== spring boot 源码分析 ==== spring session ==== spring cloud ==== spring jpa ==== spring jta == 数据库 === 数据库调优 === 索引 === 数据库隔离级别 spring 事务隔离级别 === mysql分布式 == Nosql === redis ==== redis原理 ==== redis常用数据结构 ==== redis事务 ==== redis cluster ==== redis 消息 === cassandra ==== cassandra特性 ==== cassandra底层实现 ==== cassandra数据结构 == zookeeper ==== zookeeper 特性 ==== zookeeper 底层原理 == 分布式相关 === cap base理论 === 分布式锁以及实现 === 分布式事务 === 分布式ID生成 === 分布式缓存 === RMI == jms === jms概念 === ActiveMq === RocketMq === kafka === ZeroMq RabbitMq === 其它 == 算法 === 排序算法 ==== shell 排序 ==== 归并排序 ==== 快速排序 ==== 堆排序 ==== 分配排序 ==== 基排序 === 树 ==== 遍历方式 ==== 二叉树 ==== BTTree ==== Red-Black tree === 图 ==== 图的遍历 ==== 最短路径 === 一致性Hash算法 === 数据一致性与paxos算法 === 加密算法 == 区块链 === 特性 === 分布式数据存储 === 点对点传输 === 共识机制 === 加密算法 == 深度学习 === 原理 === 常见实现