一、集合问题
1、问题:ArrayList,Vector, LinkedList 区别
1)、数据结构不同:ArrayList和Vector采用动态数组(ArrayList默认10扩容0.5,Vector默认扩容1倍),LinkedList采用链表方式
2)、线程安全:Vector类方法带有synchronized关键字保证线程安全,性能比ArrayList差
3)、性能比较:LinkedList 增删改快,ArrayList查询快,Vector线程安全较慢
4)、选择:单线程使用ArrayList和LinkedList,多线程建议使用Collections工具类,vector官方已不建议使用,属于Java中的遗留容器(遗留容器还有Hashtable、Dictionary、BitSet、Stack、Properties)
2、快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别?【这里用iterator举例,其他还有hashmap等不一一举例说明】
引用:https://blog.csdn.net/zzy7075/article/details/105843866
1)、fail-fast:通过抛出ConcurrentModificationException异常来触发的
2)、fail-safe:没有抛出ConcurrentModificationException异常:
当多个线程对Collection进行操作时,一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
fast-fail解决办法:通过util.concurrent集合包下的相应类去处理。new ArrayList<String>()替换成new CopyOnWriteArrayList<String>()。因为ArrayList继承AbstractList,当调用 next() 和 remove()时,都会执行 checkForComodification()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
延伸:ArrayList和CopyOnWriteArrayList的区别
(1) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。
(2) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
(3) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!
3、hashmap数据结构、工作原理、扩容机制
1)、数据结构
采用Entry数组来存储key-value对(数组默认大小为16),每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,依次来解决Hash冲突的问题,因为HashMap是按照Key的hash值来计算Entry在HashMap中存储的位置的,如果hash值相同,而key内容不相等,那么就用链表来解决这种hash冲突。
Entry数组:hashmap主体组成单元,包含一个key-value键值对和next指针
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
线性链表:hash冲突链表
二叉树:红黑树
哈希表:根据哈希函数f(key)计算实际存储位置
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2)、属性变量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//装载因子 默认0.75
static final int TREEIFY_THRESHOLD = 8;//数组容量,链表长度大于8可能转红黑树(元素小于MIN_TREEIFY_CAPACITY 优先扩容)
static final int UNTREEIFY_THRESHOLD = 6;//红黑树节点数小于6,转为链表
static final int MIN_TREEIFY_CAPACITY = 64;//数组容量大于等于64且链表长度大于8就转红黑树
2.1、如果 链表的长度 大于 8(TREEIFY_THRESHOLD ) 会尝试调用 treeifyBin 方法
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
2.2、如果表的长度小于 64(MIN_TREEIFY_CAPACITY ) 会先扩容
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> 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);
}
}
详细内容:JDK1.8源码中的HashMap_cainiaoblog的博客-CSDN博客
3)、put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //是否初始化
n = (tab = resize()).length; //扩容初始化:return (Node<K,V>[])new Node[newCap]
if ((p = tab[i = (n - 1) & hash]) == null) //Ebtry[] hash位置为null直接赋值
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> 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<K,V>)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);
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;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
4)、get方法,通过hash获取对应位置数据,hash冲突通过getTreeNode获取
public V get(Object key) {
Node<K,V> e;
return (e = getNode(key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods.
*
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & (hash = hash(key))]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
5)、remove移除
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
hashmap面试问题引用:http://www.52xingchen.cn/detail/49
3、List Set Map 存取数据区别
1)、结构
List和Set存储单列数据,Map存储键值对数据
List数据有序且允许重复;Set无序(除了TreeSet)且不允许重复;Map无序,键不允许重复,值允许重复
2)、实现
2.1、 List接口
LinkedList:
链表,内存是散列的,增删快,查找慢
ArrayList:
数组,非线程安全,增删慢,查找快;
Vector:
数组,线程安全,增删慢,查找慢;
2.2、Set接口
HashSet:根据元素hashcode存放,底层是散列表(Node[]数组 + 单链表 + 红黑树)
TreeSet:利用红黑树进行排序,底层是二叉树
LinkedHashSet:根据元素hashcode存放,内部维护了一个双向链表
2.3、Map接口
HashMap:根据hashcode存放数据,线程不安全,支持null值和null键
HashTable:根据hashcode存放数据,线程安全,不支持null值和null建
LinkedHashMap: 根据元素hashcode存放,内部维护了一个双向链表(hashMap+linkList)
TreeMap:根据键利用红黑树进行排序,默认是键值的升序排序
3)、取数据
3.1、List
循环:for foreatch Iterator迭代器
获取:get(index)、poll()、peek()
3.2、Set
循环:foreatch Iterator迭代器
3.3、Map
循环:foreatch entrySet() keySet()
获取:get(key)
4、Array和ArrayList区别
4.1、空间
Array指定空间大小,不可扩展
ArrayList动态空间,0.5倍扩展,newCapacity=oldCapacity+(oldCapacity>>1)
4.2、存储数据
Array存储基础类型和对象类型
ArrayList只存储对象类型
4.5、方法
Array没有删除方法
ArrayList删除remove方法
哈罗
-
数据结构与查询需求:
-
如果数据结构简单,主要是结构化数据,并且需要执行复杂的查询操作,包括联接、分组、排序等,那么MySQL可能是更好的选择。MySQL具有强大的查询性能和丰富的查询功能,适用于处理大量结构化数据。
-
如果数据结构复杂,包含大量非结构化数据(如文本、日志等),并且需要全文搜索、模糊匹配等功能,那么ES是更适合的选择。ES专为全文搜索而设计,具有强大的全文搜索功能和灵活的数据类型。
-
-
数据实时性:
-
如果需要实时搜索和分析数据,即数据需要实时更新并能够快速返回搜索结果,那么MySQL可能更适合。MySQL具有较好的实时性,可以快速返回查询结果。
-
如果数据更新不是实时的,或者不需要立即获得最新的搜索结果,那么ES是更好的选择。ES的倒排索引结构使其在处理大量数据时具有优秀的搜索性能,同时ES还支持近实时搜索。
-
-
扩展性与可维护性:
-
如果需要处理的数据量很大,并且需要横向扩展以支持更多的查询请求,那么ES是更好的选择。ES是分布式搜索引擎,可以方便地扩展集群规模以处理更大的数据量和并发请求。
-
如果数据量不大,只需要单个数据库实例即可处理,并且关注的是一致性和事务性,那么MySQL可能更合适。MySQL是关系型数据库管理系统,具有较好的事务一致性和ACID属性。
-
-
分析与监控:
-
如果需要进行复杂的数据分析,包括统计、聚合、趋势预测等,那么选择ES可能更好。ES提供了丰富的分析功能和监控工具,可以帮助你更好地理解数据和性能。
-
如果只需要基本的统计分析功能,那么MySQL可能更适合。MySQL提供了基本的统计功能和性能监控工具。
-
-
成本与资源:
-
如果对成本敏感,并且希望最大化资源利用效率,那么选择ES可能更好。ES是开源的分布式搜索引擎,可以节省成本并充分利用现有的计算资源。
-
如果已经拥有MySQL许可证并且希望继续使用MySQL作为主要的数据存储和处理工具,那么选择MySQL可能更合适。MySQL是一个成熟的关系型数据库管理系统,需要购买相应的许可证。
-
-
索引优化:为经常用于查询条件的列创建索引,可以显著提高查询速度。索引可以加快查询的执行速度,并减少数据库服务器的负载。但是需要注意的是,索引也会占用一定的存储空间,并且在插入、更新和删除操作时可能会降低性能。
-
分区表:将一个大表分为多个较小的分区,可以更有效地管理数据并提高查询性能。每个分区可以单独存储和检索数据,并且可以使用更高效的数据结构和算法来加速查询。
-
缓存查询结果:如果查询结果不经常变化,可以将查询结果缓存起来,以减少每次查询时都需要执行的计算和网络传输。缓存可以使用各种技术实现,例如内存缓存、分布式缓存等。
-
优化查询语句:编写高效的查询语句可以减少数据库服务器的负载并提高查询速度。例如,避免使用子查询、减少全表扫描等。
-
使用索引扫描:在查询语句中使用索引扫描可以加快查询速度。索引扫描会利用创建的索引来查找满足查询条件的数据行,而不是全表扫描。
-
调整数据库配置:根据具体的应用场景和硬件环境,调整数据库服务器的配置可以提高查询性能。例如,增加缓冲区大小、调整连接数等。
-
使用分布式数据库:将数据分散到多个数据库服务器上可以提高查询性能和可扩展性。分布式数据库可以通过负载均衡和分片技术来分配数据和查询请求。
-
主键索引(PRIMARY KEY):主键索引是唯一且非空的索引,通常在表中自动创建。
-
唯一索引(UNIQUE KEY):唯一索引类似于主键索引,不同之处在于一个表可以有多个唯一索引。
-
普通索引(INDEX):普通索引是最基本的索引类型,它没有任何限制。
-
全文索引(FULLTEXT):全文索引用于全文搜索,它提供了高级的搜索功能。
-
列顺序:如果查询中引用的列顺序与联合索引的顺序不匹配,那么联合索引将不会生效。
-
查询条件:如果查询条件中引用了联合索引中未包含的列,那么联合索引将不会生效。
-
过滤条件:如果查询中使用了某些过滤条件,如函数或表达式,那么联合索引将不会生效。
-
NULL值:如果联合索引中包含NULL值,那么查询优化器可能会选择不使用该索引。
-
事务传播级别不匹配:如果当前线程的事务传播级别与预期的事务传播级别不匹配,那么事务将不会生效。
-
事务隔离级别不匹配:如果当前线程的事务隔离级别与预期的事务隔离级别不匹配,那么事务将不会生效。
-
事务超时:如果事务的执行时间超过了设定的超时时间,那么事务将自动回滚。
-
事务异常:如果在事务执行过程中发生了未处理的异常,那么事务将自动回滚。
-
事务已提交:如果当前线程的事务已经提交,那么无法再次提交该事务。
-
数组(Array):一种线性数据结构,用于存储相同类型的元素。
-
链表(LinkedList):一种双向链表,每个节点包含一个数据域和一个指向下一个节点的指针。
-
栈(Stack):一种后进先出(LIFO)的数据结构,支持插入和删除操作。
-
队列(Queue):一种先进先出(FIFO)的数据结构,支持插入和删除操作。
-
散列表(HashMap):一种基于键值对映射的数据结构,通过哈希函数将键映射到桶中。
-
树(Tree):一种层次结构,每个节点可以有多个子节点。
-
图(Graph):由节点和边组成的数据结构,表示对象及其之间的关系。
![](https://img-blog.csdnimg.cn/direct/f7a57c75f3ae4d878504d1d2cf6c6898.png)
-
List(列表):List是一个有序集合,元素可以重复,并且可以插入或删除元素。它通常用于维护元素的特定顺序,例如需要按照特定顺序访问元素的场景。
-
Set(集合):Set是一个无序集合,元素不能重复,并且不能插入或删除元素。它通常用于去除重复元素,例如需要对一组值进行去重操作的场景。
-
不可变性 :一旦创建了一个字符串,就不能改变它。例如,你不能改变字符串中的特定字符。不过,你可以创建一个新的字符串来代替原有的字符串。
-
共享 :多个String对象可以共享相同的字符数组。例如,当你执行 "hello" + "world" 时,会创建一个新的String对象,但两个对象共享同一段字符数组。
-
可变长 :字符串的长度是可变的,可以容纳任何长度的字符序列,包括空字符串。
-
线程安全 :String类是不可变的,因此它是线程安全的。一旦一个字符串被创建,其内容就不能被修改,这就意味着多个线程可以同时读取或使用同一个字符串,而不会产生任何问题。
-
预定义 :Java中已经预定义了一些常用的字符串操作,如获取子串、查找、替换等。
-
字符串的内容不可变。也就是说,一旦一个字符串被创建,其内容就不能被修改。例如,你不能改变一个字符串中的特定字符。你可以创建一个新的字符串来代替原有的字符串,但原有的字符串仍然存在。
-
字符串是不可变的,也意味着它是线程安全的。因为其内容不能被修改,所以不存在线程竞争条件。这就意味着多个线程可以同时读取或使用同一个字符串,而不会产生任何问题。
-
提高性能:对于频繁创建和销毁的对象,缓存可以避免重复创建和销毁的开销,从而提高应用程序的性能。
-
节省内存:对于一些资源有限的场景,如移动设备或内存受限的环境,使用缓存可以减少内存占用。
-
特定功能:某些包装类可能需要缓存其内部状态以提供特定的功能。例如,Integer 类缓存了 -128 到 127 的所有实例,这是因为这个范围内的整数在内存中占用空间小且频繁使用,通过缓存可以避免重复创建和销毁的开销。
-
复杂性增加 :多继承会增加代码的复杂性。一个类需要理解和遵守多个父类的接口,这可能会导致混乱和错误。
-
菱形问题(Diamond Problem) :在多继承中,当一个类继承多个父类时,可能会出现菱形问题(Diamond Problem)。这个问题发生在当两个父类都继承自一个共同的基类,并且子类需要通过继承来同时获得这两个父类的特性时。这种情况下,编译器可能会无法确定应该使用哪个版本的基类方法。
-
资源管理 :在Java中,当一个对象不再被引用时,它会被垃圾收集器收集。但是,在多继承的情况下,对象何时被垃圾收集可能会变得复杂,因为可能有多个父类仍然持有对该对象的引用。
-
设计问题 :多继承可能会鼓励“过度设计”,即过度复杂化和不清晰的设计。这可能会导致代码难以理解和维护。
蔚来
一、MYSQL专题: 脏读、幻读、不可重复读区别及解决方案
1、脏读 dirty read(读到未提交的数据)
二、索引失效场景有哪些
![](https://img-blog.csdnimg.cn/direct/a26be0c75f4b43e4a3d979e9d00a6190.png)
-
-
id — 选择标识符,id 越大优先级越高,越先被执行;
-
select_type — 表示查询的类型;
-
table — 输出结果集的表;
-
partitions — 匹配的分区;
-
type — 表示表的查询类型;
-
possible_keys — 表示查询时,可能使用的索引;
-
key — 表示实际使用的索引;
-
key_len — 索引字段的长度;
-
ref— 列与索引的比较;
-
rows — 大概估算的行数;
-
filtered — 按表条件过滤的行百分比;
-
Extra — 执行情况的描述和说明。
-
-
all — 扫描全表数据;
-
index — 遍历索引;
-
range — 索引范围查找;
-
index_subquery — 在子查询中使用 ref;
-
unique_subquery — 在子查询中使用 eq_ref;
-
ref_or_null — 对 null 进行索引的优化的 ref;
-
fulltext — 使用全文索引;
-
ref — 使用非唯一索引查找数据;
-
eq_ref — 在 join 查询中使用主键或唯一索引关联;
-
const — 将一个主键放置到 where 后面作为条件查询,
-
const、system、NULL指查询优化到常量级别, 甚至不需要查找时间.
-
MySQL 优化器就能把这次查询优化转化为一个常量,如何转化以及何时转化,这个取决于优化器,这个比 eq_ref 效率高一点。
![](https://img-blog.csdnimg.cn/direct/74de7ffb91e04796acca1d229c173fea.png)
![](https://img-blog.csdnimg.cn/direct/84d75825823849f9986302f74e176100.png)
![](https://img-blog.csdnimg.cn/direct/be79c1f5ef4c4bfeb718b5a9bad84578.png)
![](https://img-blog.csdnimg.cn/direct/1158dfb9a83b42989bf75bf3af5c418e.png)
/*
* CyclicBarrier
* */
public static void cyclicMethod(){
String printStr = "ABC";
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
Runnable tesk = new Runnable() {
@Override
public void run() {
for (int i = 0; i < printStr.length(); i++) {
synchronized (this) {
sharedCounter = sharedCounter > 2 ? 0 : sharedCounter;
System.out.println(printStr.toCharArray()[sharedCounter++]);
}
}
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(tesk).start();
new Thread(tesk).start();
new Thread(tesk).start();
}
/*
* CountDownLatch
* */
public static void countDownMethod(){
String printStr = "ABC";
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable tesk = new Runnable() {
@Override
public void run() {
for (int i = 0; i < printStr.length(); i++) {
synchronized (this) {
sharedCounter = sharedCounter > 2 ? 0 : sharedCounter;
System.out.println(printStr.toCharArray()[sharedCounter++]);
}
}
try {
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(tesk).start();
new Thread(tesk).start();
new Thread(tesk).start();
}
一、线程池的几种创建方式
1、Executors 执行器创建线程
1)、Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2)、Executors.newCachedThreadPool
3)、Executors.newSingleThreadExecutor
4)、Executors.newScheduledThreadPool
5)、Executors.newSingleThreadScheduledExecutor
6)、Executors.newWorkStealingPool
2、ThreadPoolExecutor 线程执行器创建线程(核心线程数,最大线程数,最大线程存活时间,存活时间单位,阻塞队列,线程工厂和拒绝策略可不传)默认策略为 AbortPolicy:拒绝并抛出异常
二、锁升级过程
无锁:初始状态
一个对象被实例化后,如果还没有被任何线程竞争锁,那么它就为无锁状态(01)。
偏向锁:单线程竞争
当线程A第一次竞争到锁时,通过CAS操作修改Mark Word中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。
轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争
如果线程B再去竞争锁,发现偏向线程ID不是自己,那么偏向模式就会立刻不可用。即使两个线程不存在竞争关系(线程A已经释放,线程B再去获取),也会升级为轻量级锁(00)。
重量级锁:同一时刻多线程竞争
一旦轻量级锁CAS修改失败,说明存在多线程同时竞争锁,轻量级锁就不适用了,必须膨胀为重量级锁(10)。此时Mark Word存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程必须进入阻塞状态。
三、i++保证线程安全性
使用cas(AtomicReference)、锁、synchronized保证i++原子性操作,使用AtomicStampedReference通过时间戳解决CAS ABA问题
四、HashMap和ConcurrentHashMap区别
1、线程安全
2、性能:HashMap单线程更好,多线程ConcurrentHashMap使用分段锁技术更好
3、HashMap允许null值和键,ConcurrentHashMap 不允许null 键,也允许null 值