可以在队头队尾加入、删除、访问元素
继承HashMap; 双向链表; 内部有一个LinkedHashMap.Entry<K,V>来代替HashMap.Entry<K,V>, 重写了newNode方法,在put方法中创建新节点的时调用此方法返回一个LinkedHashMap.Entry<K,V>节点;
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//前后节点
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
两个成员变量before和after可以看出,这是一个双向链表
//链表的头节点 transient LinkedHashMap.Entry<K,V> head; //链表的尾节点 transient LinkedHashMap.Entry<K,V> tail; //在get操作后,是否将该节点放入到队尾,默认值为false,可以通过构造方法设置其值public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) final boolean accessOrder;
在put(入队)方法执行时调用 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } linkNodeLast方法,负责将节点加入链表尾部,将新节点的before指向tail,然后将tail指向新节点
在get(读取)的时候调用(如果accessOrder==true则调用该方法,将当前节点移动到队尾tail)
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
如果不指定大小,可以认为是无界队列,因为队列默认大小为Integer.MAX_VALUE
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
static class Node<E> {
E item;//存储数据
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;//链表的下一个节点
Node(E x) { item = x; }
}
可以看出这一个单向链表
//队列最大存放多少个对象
private final int capacity;
//当前队列中存的对象个数
private final AtomicInteger count = new AtomicInteger();
//队列头节点
transient Node<E> head;
//队列尾节点
private transient Node<E> last;
//出队时的独占锁,对队列头加锁
private final ReentrantLock takeLock = new ReentrantLock();
//队列是否是空的,用来阻塞take方法
private final Condition notEmpty = takeLock.newCondition();
//入队时的独占锁,对队列尾加锁
private final ReentrantLock putLock = new ReentrantLock();
//队列是否是慢的用来阻塞 put方法
private final Condition notFull = putLock.newCondition();
jdk1.8之前list + 链表 jdk1.8之后list + 链表/红黑树(当链表长度到8时,转化为红黑树)
默认0.75,也就是会浪费1/4的空间,达到扩容因子时,会将list扩容一倍,0.75 是时间与空间一个平衡值;
多线程同时写入,同时执行扩容操作,多线程扩容可能死锁、丢数据;可以对HashMap 加入同步锁Collections.synchronizedMap(hashMap),但是效率很低,因为该锁是互斥锁,同一时刻只能有一个线程执行读写操作,这时候应该使用ConcurrentHashMap
core,maxPoolSize,keepalive 执行任务时
- 如果线程池中线程数量 < core,新建一个线程执行任务;
- 如果线程池中线程数量 >= core ,则将任务放入任务队列 (由此可见线程池的队列如果是无界队列,那么设置线程池最大数量是无效的);
- 如果线程池中线程数量 >= core 且 < maxPoolSize,则创建新的线程;
- 如果线程池中线程数量 > core ,当线程空闲时间超过了keepalive时,则会销毁线程;
-
Executors.newFixedThreadPool(10); 固定大小的线程池: 它的实现new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()); 初始化一个指定线程数的线程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,当线程池没有可执行任务时,也不会释放线程。 由于LinkedBlockingQuene的特性,这个队列大小默认是Integer.MAX_VALUE,若消费不过来,会导致内存被任务队列占满,最终oom;
-
Executors.newCachedThreadPool(); 缓存线程池: 它的实现new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue()); 初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销,因为线程池的最大值了Integer.MAX_VALUE,会导致无限创建线程;所以,使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程会导致严重的性能问题;
-
Executors.newSingleThreadExecutor() 单线程线程池: 同newFixedThreadPool线程池一样,队列用的是LinkedBlockingQueue,队列大小默认是Integer.MAX_VALUE,可以无限的往里面添加任务,直到内存溢出;
- cpu管线导致指令重排序
- jvm编译器导致指令重排序
as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
工作内存与主内存中数据不一致;
在每个volatile写操作的前面插入一个StoreStore屏障。 在每个volatile写操作的后面插入一个StoreLoad屏障。 在每个volatile读操作的前面插入一个LoadLoad屏障。 在每个volatile读操作的后面插入一个LoadStore屏障。
- 防止重排序
- 保证可见性
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
对final域的读和写更像是普通的变量访问。对于final域,编译器和处理器要遵守两个重排序规则:
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
- 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
http://ifeve.com/java-memory-model/
serverltDispatcher -- 》 doDispatcher --> 获取handler(controller,interceptor) --》获取handlerAdeptor(controller中的方法)
- new User(); 创建一个User对象,内存分配在堆上
- User user; 创建一个引用,内存分配在栈上
- = 将User对象地址赋值给引用
本问题可以扩展为懒汉单例模式怎么写才是正确的;