集合
Java中集合和数组的区别?
一、集合和数组的区别
区别1:
数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值。
集合只能存储引用数据类型(对象)。集合也能存储基本数据类型(有点矛盾,看后句),但是在存储的时候会自动装箱变成对象。
区别2:
数组长度是固定的,不能自动增长。
集合的长度是可变的,可以根据元素的增长而增长。
二、集合和数组什么时候用
1、如果元素个数是固定的推荐用数组,效率高。
2、如果元素个数不是固定的推荐用集合
三、集合和数组什么时候用的原因
部分集合内部是用数组实现(当然还有是哈希,二叉树等),当每次创建一个集合对象是,默认创建10个长度的数组。当添加到第11个时,它会按照原数组1.5倍创建,在把原来的数组值复制过去,原来10长度的数组将变成垃圾被回收。当添加到16或者更多,以后都是按照原数组1.5倍创建,复制这个过程。所以,当固定长度为100的时候,你选择了集合就是低效率,选择数组就是高效率,因为集合里面有很多创建,复制,销毁过程。
集合类了解吗?知道哪些集合?说说他们的继承关系
参考:https://www.cnblogs.com/youngao/p/12518875.html
数组和链表的区别,读写这块链表和数组的区别
内存:数组连续,链表不一定;数组随机访问效率高,链表插入效率高,数组插入会进行复制。
hash扩容,给定一个长度为1000的hashmap,存放900个元素会不会扩容、750个呢?
与负载因子有关,默认为0.75,参考:https://www.cnblogs.com/youngao/p/12518967.html
ArrayList底层怎么实现,怎么扩容,复制的话有更好的方法吗,ArrayList去除重复元素?
底层:动态数组;扩容:左移,1.5倍;后面两个问题的解决思路还不清楚
ArrayList 相应的线程安全容器,两者底层区别
线程安全:Vector;区别:参考两者底层源码分析,简要概括如下:
- ArrayList和 Vector都实现了List接口, 都是通过数组实现的。
- Vector是线程安全的,而ArrayList是非线程安全的。
- List第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当 List 认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%
ArrayList和LinkedList的区别,底层分别是怎么实现的
参考两者底层源码分析,简要概括如下:
- ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。
- LinkedList底层是通过双向链表实现的, LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(), peek(), poll()等方法。
- LinkedList更适合从中间插入或者删除(链表的特性)。 ArrayList更适合检索和在末尾插入或删除(数组的特性)。
Java对有序的ArrayList查找是否会优化?
否,参考:https://www.cnblogs.com/youngao/p/12517410.html 2.4 遍历部分
hash算法知道吗?介绍一下你知道的hash算法,hash冲突知道吗?
怎么解决hash冲突呢,讲一下开放地址法、怎么确定开放地址的位置
参考:https://www.cnblogs.com/youngao/p/12599603.html 2 hash冲突
HashMap了解吗,说一下, 实现HashMap 【不考虑红黑树】
大致思路是jdk7和jdk8的实现原理及区别(重点有实现的数据结构,存储单元从Entry到Node的转变,加载因子,什么时候扩容,jdk1.8扩容的具体实现方式等等)
hashmap如何扩容,链表尾部的节点怎么处理的?
把上面的1.8扩容具体实现抽出来讲一下即可
HashMap的底层数据结构及作用,为什么线程不安全并举例,如何改进能让线程安全?
底层:1.8 数组+链表+红黑树,1.7 数组+链表;
不安全:HashMap的线程不安全体现在会造成死循环、数据丢失、数据覆盖这些问题。其中死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而1.8中仍会有数据覆盖这样的问题。
HashMap的线程不安全主要是发生在扩容函数中,即根源是在transfer函数中,JDK1.7中HashMap的transfer函数如下:
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
这段代码是HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。理解了头插法后再继续往下看是如何造成死循环以及数据丢失的。
扩容造成死循环和数据丢失的分析过程
假设现在有两个线程A、B同时对下面这个HashMap进行扩容操作:
正常扩容后的结果是下面这样的:
但是当线程A执行到上面transfer函数的第11行代码时,CPU时间片耗尽,线程A被挂起。即如下图中位置所示:
此时线程A中:e=3、next=7、e.next=null
当线程A的时间片耗尽后,CPU开始执行线程B,并在线程B中成功的完成了数据迁移
重点来了,根据Java内存模式可知,线程B执行完数据迁移后,此时主内存中newTable和table都是最新的,也就是说:7.next=3、3.next=null。
随后线程A获得CPU时间片继续执行newTable[i] = e,将3放入新数组对应的位置,执行完此轮循环后线程A的情况如下:
接着继续执行下一轮循环,此时e=7,从主内存中读取e.next时发现主内存中7.next=3,于是乎next=3,并将7采用头插法的方式放入新数组中,并继续执行完此轮循环,结果如下:
执行下一次循环可以发现,next=e.next=null,所以此轮循环将会是最后一轮循环。接下来当执行完e.next=newTable[i]即3.next=7后,3和7之间就相互连接了,当执行完newTable[i]=e后,3被头插法重新插入到链表中,执行结果如下图所示:
上面说了此时e.next=null即next=null,当执行完e=null后,将不会进行下一轮循环。到此线程A、B的扩容操作完成,很明显当线程A执行完后,HashMap中出现了环形结构,当在以后对该HashMap进行操作时会出现死循环。并且从上图可以发现,元素5在扩容期间被莫名的丢失了,这就发生了数据丢失的问题。
JDK1.8中找不到transfer
函数,因为JDK1.8直接在resize
函数中完成了数据迁移,此外JDK1.8在进行元素插入时使用的是尾插法。不过数据又会有数据覆盖问题。
put时,数据被覆盖
在put方法中有下面这样一段代码:
if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素 tab[i] = newNode(hash, key, value, null);
假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
put时,size被覆盖
仍是put方法中的一段代码
//size为数组的当前大小 if (++size > threshold) resize();
还是线程A、B,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程A执行到上面代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程B拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程A再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程A、B都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。
ConcurrentHashMap,怎么保证线程安全,JDK1.7 和 JDK1.8的区别
这一部分可以在多线程部分的,这里先不分析
HashMap和HashTable的区别,HahsMap和HashSet的关系,要结合源码说。
参考博客中各个源码分析,简要概括如下
- 一.HashTable的方法前面都有synchronized来同步,是线程安全的;HashMap未经同步,是非线程安全的。
- 二.HashTable不允许null值(key和value都不可以) ;HashMap允许null值(key和value都可以)。
- 三.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
- 四.HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历。
- 五.HashTable中hash数组默认大小是11,增加的方式是 old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。
- 六.哈希值的使用不同,HashTable直接使用对象的hashCode; HashMap重新计算hash值,而且用与代替求模。
HashMap和TreeMap区别,为啥选用HashMap而不选用TreeMap,时间复杂度是多少。
回到效率相关的问题,HashMap(O(1)),TreeMap(O(logn))
List、Set、Map的区别、Map底层原理、Set的底层原理、TreeMap的底层结构?
参考源码分析
你能说说concurrenthashmap和hashmap的put过程吗?
红黑树你能介绍下吗?左旋右旋是怎么样的?为什么要左旋右旋呢?红黑树便于查找元素吗?
参考:https://www.cnblogs.com/youngao/p/12012087.html
线程池参数含义,cpu密集型任务用什么线程池
sleep和wait的区别,sleep和wait在调用的时候会不会出让CPU的时间片
36.哪个共享,哪个私有,共享的哪些资源?
在一个线程fork会怎么样
讲了synchronized锁,自旋锁,自适应自旋锁,它是悲观锁还是乐观锁
15、乐观锁底层实现,我说了cas,然后把aba问题,自旋锁消耗问题和解决方法说一遍
线程 sleep和wait的区别,守护线程,线程池的参数
java并发中会遇到那些问题,fork/join,线程实现,有那些多线程,核心线程数,最大线程数,拒绝策略等,线程执行过程
你了解的线程安全是什么,为什么出现,怎么避免,怎么性能更好的避免
多线程
进程与线程的区别
参考:https://www.cnblogs.com/youngao/p/12568580.html
开发过程中有用到过多线程吗,有哪些需要注意的地方,参数怎么设定
需要结合项目,这里还未准备
进程之间的通讯方式,线程间通信,怎么同步,进程和线程的信号量什么区别
参考:https://www.cnblogs.com/youngao/p/12568580.html
线程的状态以及状态变化过程
参考:https://www.cnblogs.com/youngao/p/12568580.html
什么是线程安全,什么是线程不安全
线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
类的线程安全:
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
多线程有哪些实现方式
继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。
如果我想做线程安全的话,可以有哪些实现方式?
- 对非安全的代码进行加锁控制;
- 使用线程安全的类;
- 多线程并发情况下,线程共享的变量改为方法级的局部变量。
volatile的作用,底层原理
作用简述:保证可见性和顺序性。Java把处理器的多级缓存抽象为JMM,即线程私有的工作内存和线程公有的主内存,每个线程从主内存拷贝所需数据到自己的工作内存。volatile的作用就是当线程修改被volatile修饰的变量时,要立即写入到主内存,当线程读取被volatile修饰的变量时,要立即到主内存中去读取,保证了可见性。禁止指令重排来保证顺序性。
具体分析参考:https://www.cnblogs.com/youngao/p/12558381.html 6 volatile 关键字
synchronized和lock的原理,区别,如何使用
synchronized加在不同的位置会有不同的效果吗?什么时候加在方法,什么时候加在语句块上?
synchronized和Lock的区别
- 主要相同点:Lock能完成synchronized所实现的所有功能
- 主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。Lock的锁定是通过代码实现的,而synchronized是在JVM层面上实现的,synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。Lock锁的范围有局限性,块范围,而synchronized可以锁住块、对象、类。
synchronized参考:https://www.cnblogs.com/youngao/p/12558381.html 8 synchronized 关键字概述部分
lock参考:https://www.cnblogs.com/youngao/p/12573299.html
synchronized 1.6后的优化,锁膨胀过程,每个过程中的原理
参考:https://www.cnblogs.com/youngao/p/12558381.html 8.6 synchronized 的优化
什么是死锁,如何产生,如何预防,在程序中如何分析?
死锁:
所谓死锁,是指多个线程程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:
产生死锁的原因?
可归结为如下两点:
a. 竞争资源
系统中的资源可以分为两类:
- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
- 另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞);
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。
死锁产生的4个必要条件?
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 循环等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
解决死锁的基本方法
操作系统在解决死锁问题的时候,有四个大的方向,分别是预防死锁,避免死锁,检测死锁和解除死锁。
1 预防死锁:
- 打破互斥条件:允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。
- 破坏请求条件:资源一次性分配,即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。
- 破坏不可剥夺条件:当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。
- 破坏循环等待条件:资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,进程占用了小号资源,才能申请大号资源,释放则相反,这样就不会产生环路
1、以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
如果此时把获得锁的时序改成:
那么死锁就永远不会发生。 针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生。银行家算法参考:
https://www.cnblogs.com/youngao/p/12607020.html
2、超时放弃
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
2 避免死锁:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
3 检测死锁
首先为每个进程和每个资源指定一个唯一的号码;
然后建立资源分配表和进程等待表。
死锁检测命令:
- 1、Jstack命令
- jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
- 2、JConsole工具
- Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
4 解除死锁
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
- 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
了解哪些锁介绍一下,哪些是乐观锁,哪些是悲观锁
参考:https://www.cnblogs.com/youngao/p/12573299.html
对于哪些是乐观锁,哪些是悲观锁,这里先暂定为只有自旋锁为乐观其余均为悲观锁
乐观锁原理,CAS的实现,有什么缺点
参考:
乐观锁:https://www.cnblogs.com/youngao/p/12573299.html
CAS:https://www.cnblogs.com/youngao/p/12558381.html 8.6.1 CAS
实现Unsafe类:https://www.cnblogs.com/youngao/p/12559391.html
可重入锁的原理,为什么默认是非公平锁,公平锁与非公平锁原理,对比
参考:https://www.cnblogs.com/youngao/p/12547512.html
CountDownLatch的使用,原理
hashmap是否线程安全,有什么安全的map?
ConcurrentHashMap的原理,为什么是线程安全的,让你设计怎么实现读写安全
1.7、1.8区别,put实现有什么不同?1.7当中的segement怎么实现的?
上面的三个问题本质都是ConcurrentHashMap相关的。
概述:不安全,因为在多线程同时put时或者在扩容时Put都会有线程安全问题。安全可以使用hashtable、Collections.synchronizedMap、ConcurrentHashMap这三类。但前两类都是直接在方法标签上加了synchronized,所以效率很低。而ConcurrentHashMap效率很好,在1.7中,ConcurrentHashMap是用segment数组为每个格子加锁来保证安全性。在1.8中ConcurrentHashMap和hashmap的结构完全一样,但更改了put方法。在计算了哈希值和索引后,先判断索引位置是否正在扩容,如果正在扩容就调用一个协助扩容的函数,如果没扩容再判断是否为空,为空则用CAS的方式放入,不为空则用synchronized锁住格子,判断为链表还是红黑树,分别调用对应方式放入。最后再判断一次冲突长度,大于8则转化为红黑树。
参考:
1.7 https://www.cnblogs.com/youngao/p/12610662.html
1.8 https://www.cnblogs.com/youngao/p/12573882.html
讲一下AQS原理及实现
参考:
AQS原理
https://www.cnblogs.com/youngao/p/12547505.html
https://www.cnblogs.com/youngao/p/12547516.html
知道多线程,多进程吗?介绍一下怎样创建多线程,多进程?请问与java的区别是?
暂定
ThreadLocal 原理
参考:https://www.cnblogs.com/youngao/p/12613660.html
线程池原理,如何创建,参数(最大线程数,核心线程数等)、执行过程、拒绝策略
线程池满了咋办 ,线程池的使用有什么不好的地方?
讲讲Executor提供了哪些线程池,区别
说下线程池和普通线程的区别
写了一个BlockingQueue的生产者和消费者模型
运用生成者-消费者遍历所有文件
public class FileCrawler { private static final int FILE_QUEUE_SIZE = 10; private static final File DUMMY = new File(""); private static BlockingQueue<File> queue = new ArrayBlockingQueue<>(FILE_QUEUE_SIZE); // 递归遍历目录,将所有文件放入队列 public static void enumerate(File directory) throws InterruptedException { File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { enumerate(file); } else { queue.put(file); System.out.println("生产:" + file.getName()); } } } public static void main(String[] args) { try (Scanner in = new Scanner(System.in)) { System.out.print("输入一个文件目录:"); String directory = in.nextLine(); // 生产者线程,从目录中搜索文件放入队列 Runnable enumerator = () -> { try { enumerate(new File(directory)); queue.put(DUMMY); } catch (Exception e) { } }; new Thread(enumerator).start(); // 消费者线程,从队列中取出目录 Runnable searcher = () -> { try { boolean done = false; while (!done) { File file = queue.take(); System.out.println("消费:" + file.getName()); if (file == DUMMY) { queue.put(file); done = true; } } } catch (Exception e) { } }; new Thread(searcher).start(); } } }
3.sleep为什么是静态的
7.Java的Timer类是用来干嘛的,是如何使用的(我说没用过,但是线程池创建里有一个参数)
讲一下同步队列(貌似要我讲ArrayBlockingQueue,我凑合答了AQS原理)
有十个线程,现在有五个线程要进来,怎么做到这个约束?
-
简单说下Activity的声明周期
-
了解过哪个声明周期内不适合做耗时操作吗
-
用过哪些数据存储方式
内存里的堆和栈有什么区别?
线程池的原理,线程池的状态,这部分面试官是想跟协程比较来着
- 一个拥有的锁的线程是什么状态的
0