HashMap源码
代码语言:javascript复制❝问:「HashMap」底层原理,为什么线程不安全。❞
hashmap:
数组 链表 红黑树
初始长度 = 16
扩容因子 = 0.75
索引确定:
index = hashCode(key) % length
hashCode(key) 高8位与低8位异或 & (length - 1)
「关于线程不安全」
HashMap会进行resize操作,在resize操作的时候会造成线程不安全。下面将举两个可能出现线程不安全的地方。
put
的时候导致的多线程数据不一致。这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
另外一个比较明显的线程不安全的问题是HashMap的get操作可能因为resize而引起死循环(cpu100%)。
❝问:HashMap与Hashtable的区别❞
1、继承的父类不同
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
2、线程安全性不同
javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。
4、key和value是否允许null值
其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。
通过上面的ContainsKey方法和ContainsValue的源码我们可以很明显的看出:
Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
ConcurrentHashMap源码
❝问:ConcurrentHashMap底层原理,如何保证线程安全的❞
这里只讨论JDK1.8的ConcurrentHashMap
采用了「数组 链表 红黑树」的实现方式来设计。
采用Node节点保存key,value及key的hash值。如下:
代码语言:javascript复制static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
...
}
为保证线程安全,采用synchronized CAS HashEntry 红黑树。
无锁化保证线程安全。
我们看到put
方法调用了casTabAt
方法。
private static final sun.misc.Unsafe U;
////
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) ABASE, c, v);
}
////
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
会后调用的是本地方法。
❝问:CAS底层原理❞
引用来源:https://youzhixueyuan.com/concurrenthashmap.html
CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
AQS原理
❝问:AQS底层以及相关的类❞
见文章:Java并发编程初探-AQS
❝问:ThreadLocal底层,软引用和弱引用❞
引用来源:使用ThreadLocal怕内存泄漏?那你应该来看看这篇文章
线程池
❝问:线程池组成原理,线程池的拒绝策略❞
见文章:手写线程池
❝问:如何理解多线程,高并发❞
引用来源:https://www.cnblogs.com/cheyunhua/p/10530023.html
高并发可以通过分布式技术去解决,将并发流量分到不同的物理服务器上。但除此之外,还可以有很多其他优化手段:比如使用缓存系统,将所有的,静态内容放到CDN等;还可以使用多线程技术将一台服务器的服务能力最大化。
「多线程是指从软件或者硬件上实现多个线程并发执行的技术」,它更多的是解决CPU调度多个进程的问题,从而让这些进程看上去是同时执行(实际是交替运行的)。
这几个概念中,「多线程解决的问题是最明确的,手段也是比较单一的,基本上遇到的最大问题就是线程安全」。在JAVA语言中,需要对JVM内存模型、指令重排等深入了解,才能写出一份高质量的多线程代码。
❝问:如果有个线程4,要等前面线程1,2,3都执行完才能执行,你要怎么做❞
示例:
代码语言:javascript复制/**
* Description:倒计数器
*
* @author Lvshen
* @version 1.0
* @date: 2020/4/16 14:28
* @since JDK 1.8
*/
public class CountDownLatchDemo {
static final int COUNT = 20;
static CountDownLatch cdl = new CountDownLatch(COUNT);
public static void main(String[] args) throws Exception {
new Thread(new Teacher(cdl)).start();
Thread.sleep(1);
for (int i = 0; i < COUNT; i ) {
new Thread(new Student(i, cdl)).start();
}
synchronized (CountDownLatchDemo.class) {
CountDownLatchDemo.class.wait();
}
}
static class Teacher implements Runnable {
CountDownLatch cdl;
Teacher(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("老师发卷子。。。");
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老师收卷子。。。");
}
}
static class Student implements Runnable {
CountDownLatch cdl;
int num;
Student(int num, CountDownLatch cdl) {
this.num = num;
this.cdl = cdl;
}
@Override
public void run() {
System.out.println(String.format("学生(%s)写卷子。。。",num));
//doingLongTime();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("学生(%s)写卷子。。。",num));
cdl.countDown();
}
}
}
public class ConcurrentTestDemo {
public static void main(String[] args) {
//并发数
int currency = 20;
//循环屏障
CyclicBarrier cyclicBarrier = new CyclicBarrier(currency);
for (int i = 0; i < currency; i ) {
new Thread(() -> {
OrderServiceImplWithDisLock orderService = new OrderServiceImplWithDisLock();
System.out.println(Thread.currentThread().getName() "====start====");
//等待一起出发
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
orderService.createOrder();
}).start();
}
}
}
❝问:i 要线程安全,你有几种方法,这几种哪种性能最好❞
volatile sychronized/lock.lock , AtomicInteger
高并发下lock.lock
性能要好,atomicInteger.incrementAndGet()
底层用了「CAS」,lock.lock
底层也用了「CAS」。
public class VolatileAtomicTest {
public static volatile int num = 0;
public synchronized static void increase() {
num ;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i ) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j ) {
increase();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println(num);
}
}
❝问:线程间的共享怎么实现❞
Callable的call方法有返回值;volatile关键字能实现线程变量的可见
代码语言:javascript复制public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = () -> {
log.info("当前线程:{}", Thread.currentThread().getName());
return "Lvshen";
};
//MyFutureTask<String> myFutureTask = new MyFutureTask(callable);
FutureTask<String> myFutureTask = new FutureTask<>(callable);
new Thread(myFutureTask).start();
System.out.println(String.format("当前线程:[%s],取出的值:[%s]", Thread.currentThread().getName(), myFutureTask.get()));
}
如上代码:Main线程获取到[Thread-0]线程的值。
代码语言:javascript复制❝问:
ThreadPoolExecutor
参数❞
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
corePoolSize
:核心线程数maximumPoolSize
:最大线程数keepAliveTime
:当没有任务时,多余核心线程数的线程存活时间
Java锁
❝问:synchronized与Lock的区别❞
见文章:Java锁-synchronized底层原理
❝问:volatile关键字原理,以及原子自增类❞
见文章:Java关键字——volatile底层原理分析
❝问:JUC并发包❞
JDK并发工具类是JDK1.5引入的一大重要的功能,体现在java.util.concurrent
包下。java.util.concurrent
包主要包含了「并发集合类,线程池」和「信号量」三组重要工具类,还包括了java.util.concurrent.atomic
以及java.util.concurrent.locks
两个子包。一般来说,我们称这个包为J.U.C。
JVM
❝问:Java虚拟机内存划分,GC回收算法。❞
关于内存划分:
可以看看这篇文章:https://mp.weixin.qq.com/s/fit90VdZUa2pG9lbET0i7w
关于GC回收算法:
见文章:GC回收算法
❝问:内存溢出如何排查❞
见文章:https://mp.weixin.qq.com/s/7XGD-Z3wrThv5HyoK3B8AQ
❝问:虚拟机调优❞
见视频:https://ke.qq.com/user/index/index.html#/plan/cid=2770807&term_id=102879437
❝问:类加载❞
算法
代码语言:javascript复制❝问:雪花算法,原理知道吗,有没有缺点。❞
long id2Long = ((nowTimestamp - baseTimestamp) << TIMESTAMP_LEFT_SHIFT) | workId | sequence;
ip 端口 时间戳 。 跟机器时间有关,如果机器时间回调,会生成重复的ID。
❝问:说说二叉树,与B Tree的区别❞
见文章:MySQL为什么选择B Tree做索引
❝问:红黑树和哈希表使用场景❞
「Hash:」
hash表使用场景:bitmap的布隆过滤器使用的是hash表。在那些需要一次一次遍历,去寻找元素的问题中,可以将问题转化为根据元素的内容去寻找索引,哈希表在这方面的时间效率是贼高的;在一些字符串词频统计问题、数独问题等问题中,可以利用哈希函数来计算某个元素出现的次数,作为算法的辅助工具;还有些问题,可以利用散列函数的思路,让几个不同的元素获得同样的结果,从而实现一个聚类。
举个用于消息摘要例子,银行的数据库中是不能保存用户密码的原文的,只能保存密码的hash值。在这种应用场景里,对于抗碰撞和抗篡改能力要求极高,对速度的要求在其次。一个设计良好的hash算法,其抗碰撞能力是很高的。
「红黑树:」
- 「epoll」的事件管理模块
- Java中的
TreeMap
- 适用增删比较多的情况
- 「AVL」适用查询比较多的情况
- 相对于跳表,红黑树不适用范围性的查找
未完待续~