事务隔离级别主要有以下几种
事务隔离级别
两个基本原则:
设定堆内存大小,这是最基本的:
设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代
设定垃圾回收器
列出JVM进程
监视虚拟机运行状态信息,使用方式:
jstat -<option> <pid> [interval[s|ms]]
选项 作用
每隔1秒输出一次JVM运行信息:
jstat -gc 28389 1s
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
52416.0 52416.0 4744.9 0.0 419456.0 28180.6 2621440.0 439372.6 131072.0 33564.8 160472 1760.603 61 2.731 1763.334
如果重写了equals方法,则一定要重写hashCode方法
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)
List接口 和 Set接口 都继承了java.util.Collection接口,Map接口没有继承java.util.Collection接口; HashMap不保证数据有序,LinkedHashMap保证数据可以保持插入顺序,而如果我们希望Map可以保持key的大小顺序的时候,我们就需要利用TreeMap了
HashMap 非线程安全,多线程情况下,扩容操作可能会导致死循环
数组:查找快,插入删除慢;链表:插入删除快,查找慢。HashMap可以看作是这两种数据结构的综合,底层实现基于数组 + 链表。 当我们put一个元素的时候,将key对应的hashCode做hash,得到一整数,然后再用这个整数对数组长度进行取模操作得到index,这样就能保证key能尽量均匀的分布在数组中,这里的取模操作为了提高效率,运用了一些技巧,使用了&运算 (n - 1) & hash,所以要求数组的长度是2的指数倍,要不然也没有办法用这种方式进行取模运算。这个过程可能会产生hash碰撞,即两个不一样的key有相同的hash值,这也意味着数组某个元素对应的hash值和当前要插入key的hash值相同。这时候需要判断,如果hash相同并且 key.equals 方法返回true,则覆盖;如果已存在元素是单个Node,转换成链表;如果存在的元素对应的是链表,转换成红黑树。反正如果hash相同并且 key.equals 方法返回true,则覆盖元素;否则,就是链表或者红黑树。
/**
* The default initial capacity - MUST be a power of two.
* 初始容量 2^4
*/
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.
* 最大容量 2^30
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 负载因子 0.75。当map中的元素个数 大于capacity*load factor时就需要调整buckets的数目为当前的2倍
*/
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 number of key-value mappings contained in this map.
*/
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
* 修改次数
*/
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
*
* HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
*/
// (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;
Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化 hashCode 相同,则 equals方法不一定会返回true;equals返回true,hashCode则一定相同
当产生hash碰撞的时候,需要借助key.equals()方法去链表或树中去查找对应的节点是否已经存在,存在就覆盖,不存在就插入到链表或者树中
有关于插入链表头部还是尾部:jdk1.8之前是插入头部的,在jdk1.8中是插入尾部,HashTable一直都是插头部
当put时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。在resize的过程,简单的说就是把bucket扩充为2倍,之后重新计算index,把节点再放到新的bucket中。 java7 和 java8 的扩容机制不太一样,主要体现在计算元素在New Entry中的下标时的优化 相同点:初始化一个新的Entry数组为之前的2倍,将Old Entry里的数据拷贝到 New Entry中,并重新计算阈值(threshold = (int)(newCapacity * loadFactor))。 不同点:不同点在于数据拷贝的这个过程中,在java7中,是通过重新计算的方式确定每个元素在New Entry中的下标,重新计算,意味着小标可能完全变了,因为下标是通过取模计算出的,New Entry的长度是Old Entry的两倍,下标可能就变了。不过变不变无所谓,主要是这里重新计算了一次,效率低;在java8这部分内容做了优化,因为New Entry是通过2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。所以在java8扩充HashMap的时,不需要像java7那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+16”。
在Java8 中,如果一个桶中的元素个数超过 TREEIFY_THRESHOLD(默认是 8 ),就使用红黑树来替换链表,从而提高速度,这部分内容挺复杂。
非线程安全,LinkedHashMap是Hash表和链表的实现,并且依靠着双向链表保证了迭代顺序是插入的顺序。 LinkedHashMap 是 HashMap 的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用 LinkedHashMap。 根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用 get 方法)的链表。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。
非线程安全,HashMap不保证数据有序,LinkedHashMap保证数据可以保持插入顺序,TreeMap可以保持key的大小顺序,底层基于红黑树实现。适用于按自然顺序或自定义顺序遍历键(key)
public static void test() {
TreeMap<String, String> tmp = new TreeMap<String, String>();
tmp.put("b", "bbb");
tmp.put("c", "ccc");
tmp.put("d", "cdc");
tmp.put("a", "aaa");
Iterator<String> iterator_2 = tmp.keySet().iterator();
while(iterator_2.hasNext()){
String key = iterator_2.next();
System.out.println("tmp.get(key) is :" + tmp.get(key));
}
}
输出结果
tmp.get(key) is :aaa
tmp.get(key) is :bbb
tmp.get(key) is :ccc
tmp.get(key) is :cdc
线程安全
List接口 和 Set接口 都继承了java.util.Collection接口,Map接口没有继承java.util.Collection接口; 不能存重复的值,对于添加到Set中的元素,需要重写hashCode和equals方法。
非线程安全,因为基于HashMap实现。没有排序,不重复,可以存null
非线程安全,可以按插入顺序和访问顺序排序,不可重复,可以存null
LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同
非线程安全,TreeSet基于TreeMap实现,它的作用是提供有序的Set集合,底层基于红黑树。 实现Comparable接口,对象可以比较按照插入值大小排序。
List接口 和 Set接口 都继承了java.util.Collection接口,Map接口没有继承java.util.Collection接口;
非线程安全,在执行 add 和 remove 涉及到 i++ 和 i-- 操作。有序,可存null值,可存重复元素
// 初始化大小
private static final int DEFAULT_CAPACITY = 10;
// list已有的元素个数
private int size;
// 表示list集合的修改次数
protected transient int modCount = 0;
ArrayList默认的构造方法其实是构造了一个空数组,当进行第一次add的时候,elementData将会变成默认的长度:10
public E remove(int index) {
// 下标越界就抛异常
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
// 如果不是最后一个元素,通过System.arraycopy将该元素后面的元素向前移动一位下标
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
src:源数组; srcPos:源数组要复制的起始位置; dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度; 注意:src 和 dest 必须是同类型或者可以进行转换类型的数组
非线程安全,双向链表,插入和删除操作比 ArrayList 更加高效,随机访问的效率要比 ArrayList 差
线程安全,与ArrayList相比
线程安全,继承自Vector
ThreadLocal 在Thread类中,有这么一个属性
ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocalMap是ThreadLocal中的一个静态类,即每个线程的局部变量是存储在自己的threadLocals属性中。也就是说,每个线程都有自己的一个ThreadLocalMap对象。
可以看看,我们调用ThreadLocal的set方法时,发生了什么
public void set(T value) {
Thread t = Thread.currentThread();
// 从当前线程中获取ThreadLocalMap , 即Thread中的threadLocals属性
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 为当前线程创建一个ThreadLocalMap
createMap(t, value);
}
// 从当前线程中获取ThreadLocalMap , 即Thread中的threadLocals属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 为当前线程创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化容量为16
table = new Entry[INITIAL_CAPACITY];
// 这其实是一个取模操作,根据当前ThreadLocal对象的HashCode做hash然后 & (INITIAL_CAPACITY - 1) 得到在table数组中的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 用于计算扩容上限,即当size到达threashold时,需要resize整个Map,threshold的初始值为len * 2 / 3
setThreshold(INITIAL_CAPACITY);
}
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
有关于Entry,它继承了WeakReference,是一个弱引用,todo
在调用ThreadLoca的set方法时,从当前线程中获取threadLocals的值,如果当前线程的threadLocals为空,就创建一个ThreadLocalMap对象,并赋值给当前线程的threadLocals属性。
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
HashMap采用拉链法处理哈希冲突,即在一个位置已经有元素了,就采用链表把冲突的元素链接在该元素后面,而ThreadLocal采用的是开放地址法,即有冲突后,把要插入的元素放在要插入的位置后面为null的地方
它用于在map中移除一个不用的Entry。也是先计算出hash值,若是第一次没有命中,就循环直到null,在此过程中也会调用expungeStaleEntry清除空key节点
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
我们发现无论是set,get还是remove方法,过程中key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,GC时就会被回收。那么怎么会存在内存泄露呢?但是以上的思路是假设你调用get或者set方法了,很多时候我们都没有调用过。 所以正确的使用方式是:
死锁产生的原因
避免死锁
编程实现(变量等)、Guava cache
Memcache、redis
缓存一致性
缓存并发
如果缓存中的值为空,穿透到数据库
缓存穿透
由于缓存原因,导致大量请求到达数据库,导致雪崩。
image.png
解决方案:限流、降级、断熔
生成和消费的速度或稳定不一致。应用场景:系统解耦,比如:下单、短信、邮件等等。
好处:业务解耦、最终一致性、广播、错峰与流控。
应用拆分
elastic-job + zookeeper
apach curator + zookeeper 分布式锁实现