【Java】基础、容器、JVM、并发

参考

  1. 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
  2. 《Java并发编程的艺术》
  3. 《Java高级程序员面试笔试宝典》
  4. CyC2018/CS-Notes
  5. Snailclimb/JavaGuide

基础

1. 基本类型、包装类型、缓存池

基本类型大小(位)包装类型默认缓存池对象范围
byte8Byte-128~127
char16Character0~127
short16Short-128~127
int32Integer-128~127
float32Float
long64Long-128~127
double64Double
boolean1或32(int)Booleantrue/false
  • 以Integer为例,当Integer的内部类IntegerCache被加载时,它会创建值为-128~127的256个Integer对象,放入缓存中,当调用Integer.valueOf(...)方法时,就直接从缓存池中拿对象。
  • IntegerCache的上界可以调整,通过VM参数-XX:AutoBoxCacheMax=<size>
  • 调用==比较
    ==:基本类型比较值,引用类型比较地址
情况解释
int a;int b;都是基本类型,返回值比较的结果
int a;Integer b;将b拆箱为基本类型,返回值比较的结果
Integer a = 100;Integer b = 100a和b都是引用类型,比较地址。100在缓存区内,因此a和b都指向Integer(100)这个对象,返回True。(备注:Integer x = 100与Integer x = Integer.valueOf(100)是等价的)
Integer a = 1000;Integer b = 1000引用类型比较地址,1000没在缓存区,都返回新对象,返回False
Integer a = 100;Integer b = new Integer(100)new Integer(100)创建了新对象,与缓存区对象是不同的,返回False(备注:new Integer()在Java9中过时)

2. String、StringBuilder、StringBuffer,String Pool

字符串内容是否可变是否线程安全如何实现线程安全
String不可变只读,char数组使用final修饰,并且不公开修改char数组的方法
StringBuilder可变/
StringBuffer可变内部读写方法使用synchronized同步
  • String不可变的主要好处:线程安全实现字符串常量池的需要String作为哈希表的key,hashcode不变更安全:作为网络连接/IO操作的参数
  • 语句执行时的内存情况

可以在idea的Debug中,观察语句执行时的内存情况

语句解释
String s = “abc”在Pool中寻找"abc"对象,存在就把s指向该对象,不存在就新建对象,让s指向它。(即新创建0个或1个String对象)
String s = new String(“abc”)在Pool中寻找"abc"对象,不存在就先在Pool中创建"abc"对象。然后新建1个对象,将char数组指向Pool中的对象的char数组。(即新建(1个String对象,0个char数组)或(2个String对象,1个char数组))
String s = “abc”.intern()等价于String s = "abc"的执行情况
String s = t.intern()如果t本来就指向常量池对象,那么将s指向那个常量池对象即(s==t),如果t指向的是堆中的字符串对象,那么在常量池中寻找t的内容的对象,将s指向它
  • ==判定

明确执行时的内存情况后,判定就很简单了

情况解释
String s = “a”;String t = “a”True,s和t都指向常量池的"a"对象
String s = “a”,String t = new String(“a”)False,s指向常量池的"a",t指向堆中的"a"(备注:此时t的char[]指向s的char[],t并没有复制一份字符数据)
String x = new String(“a”),String s = x.intern(),String t = “a”True,x指向堆中的"a",x.intern()返回Pool中的"a",t也指向Pool中的"a"

3. 引用传递还是值传递?

  • 对于基本类型,参数传递的是值的拷贝,修改参数的值,不会引起原值的更变
  • 对于引用类型,参数传递的是地址的拷贝(地址也是值),修改参数的指向,不会引起原引用的指向的改变,但可以通过该地址访问到堆中的内容,更改堆中对象的数据。
main(){
	int i = 1;
	int[] nums = new int[1];
	nums[0] = 1;
	func(i,nums);
	// i = 1不变
	//nums[0]被修改为2
}
void func(int i , int[] nums){
	i = 2;//不影响原值
	nums[0] = 2;//通过引用,更新nums指向的堆中的值
	nums = new int[10];//修改引用,对原引用没影响
}

4. ==、equals()、hashCode()

  • 作用
    == : 对于基本类型判断是否相等,对于引用类型判断地址是否相等,即是否指向同一个对象。
    equals() : 如果重写了equals()方法就按照重写的逻辑判断,否则相当于==,equals表示两个对象内容上是相同的。
    hashCode() : 返回对象的hash值,Hash表会使用这个方法。
  • equals()hashCode()一定要同时被重写,因为在使用hash表时,判断两个对象是否相同,需要这两个方法都返回True
  • hashCode相同的对象,equals()不一定相同,因为同一个hashCode决定的索引位置可能会放置多个不同的对象,见下文HashMap的笔记。

5. 重写、重载、访问权限

  • 重写与重载
    – 重写override父类方法
    – 方法重载overwrite,方法名相同,参数等可以不同。
  • 访问权限
修饰符解释
public在任何位置都可以访问
protected在当前包任何位置和子类调用super访问
默认当前包内访问
private本类中访问

6. static关键字

修饰内容解释
静态变量属于类,通过类访问,内存中只存在1份
静态方法属于类,直接通过类访问,不依赖于实例
静态语句块类加载时执行一次
静态内部类创建时,不依赖于外部类的实例。非静态内部类需要外部类的实例才能创建
静态导包导包后可以直接在当前文件中使用静态变量和静态方法,不需要书写类名

8. final关键字

修饰内容解释
final变量变量值不能修改(基本类型的值不能修改;引用类型的地址不能修改,即不能指向其他对象)
final方法不能被子类重写
final类不能被继承

9. 反射

  • 每个类都有1个Class对象,可以通过这个对象获取类的字段、方法、构造器等信息。
  • 通过反射可以访问没有权限的字段、以及调用没有权限的方法。
  • 缺点:性能开销、安全限制、内部暴露。

10. 异常

  • 分为两类:Error和Exception
  • Error为无法处理的错误,如OutOfMemoryError,StackOverFlowError。
  • Exception为可以被程序处理的意料中的异常,可以通过try catch处理

11.泛型

参考Java | 关于泛型能问的都在这里了
10 道 Java 泛型面试题

  1. 什么是泛型?
  • 在定义类、接口和方法时,可以附带类型参数,使其变成泛型、泛型接口和泛型方法
//泛型方法
<T> T func(T t){}
static <T> T func(T t){}
//泛型类
class A<T>{
	T t;
	T func(T t){...}
	//下面的不可以
	//static T t
	//static T func(T t){...}
	//可以在泛型类中嵌套其他泛型方法(可以static)
	static <T> T func(T t){...}
} 
//泛型接口
interface B<T>{...}
  1. 泛型的好处?
  • 健壮(在编译时进行更强的类型检查)、简洁(消除强转,编译后自动会增加强转)、通用(代码可适用于多种类型).
  1. 什么是类型擦除机制?
  • 为了向下兼容,编译器会把泛型信息擦除,在运行时保留类型参数的上界。
  • 类型擦除只是擦除Code 属性中的泛型信息,在类常量池属性中其实还保留着泛型信息,因此可以通过反射获取泛型信息。
  1. 类型擦除的步骤?
  • 擦除所有类型参数信息,如果类型参数是有界的,则将每个参数替换为其第一个边界;如果类型参数是无界的,则将其替换为 Object
  • (必要时)插入类型转换,以保持类型安全
  • (必要时)生成桥接方法以在子类中保留多态性
  1. 泛型的限制,类型擦除的影响?
  • 无法对参数为泛型类型的方法进行重载
  • 不能在泛型类中的静态成员类型和静态方法中使用泛型参数类型。
  • 不能创建泛型数组
//不可以这样,参数类型擦除后,两个方法是重复的
void func(List<Integer> list){...}
void func(List<String> list){...}
class A<T>{
	//不可以静态成员、方法
	//static T t
	//static T func(T t){...}
}
//不可以创建泛型数组
List<String>[] arr = new List<String>[];
  1. 有哪些类型限定符?
  • <? extends T> 类型参数接受T的子类,<T extends >
  • <? super T> 类型参数接受T的父类
  • 另外<?>等价于<? extends Object>类型参数接受任意类
//定义泛型时,
//func泛型方法、类、接口接受Number的子类作为类型参数
<T extends Number> T func(T t){...}
class A<T extends Number>{...}
interface B<T extends Number>

//使用泛型时
List<? extends Number> list = new ArrayList<>();//表明list中放的是Number的子类
//一般用在方法中
void printNumber(List<? extends Number> list){...};
//调用
printNumber(Arrays.asList(1,2));
printNumber(Arrays.asList(1.0,2.0));

12.IO

IO类型解释应用场景
BIO阻塞IO,处理流数据低负载、低并发
NIO非阻塞IO,处理块数据高负载、高并发
AIO异步非阻塞IO不阻塞线程,异步回调

容器

1. ArrayList

https://blog.csdn.net/weixin_42818402/article/details/106235128

  • 扩容
  1. 默认容量为10,添加数据时,如果容量不够,会触发扩容方法grow(minCapacity)
  2. 扩容时,默认扩容为之前的1.5倍,如果这个1.5倍还小于minCapacity,那么就直接扩容到minCapacity。扩容时,新建数组,然后把旧数据拷贝进去。
  • 序列化
  1. elementData是用transient 修饰的,表明这个对象不会被自动序列化,因为elementData是一个缓冲区,真正的数据部分只占其中的一部分,不需要对其全部进行序列化。
  2. java在writeObject方法中,手动控制了只序列化elementData中有数据的部分。
  • 使用须知
  1. 如果已知ArrayList需要的大致长度,可以在创建对象时指定容量。
  2. 如果需要大量add/addAll操作,那么可以先使用ensureCapacity方法进行手动扩容,避免多次扩容带来计算开销。

2. HashMap

https://blog.csdn.net/weixin_42818402/article/details/106628340

  • Java8之前HashMap的底层实现是数组+链表,Java8之后是数组+链表+红黑树
  • 查找
    计算key的hash值,对长度取模,得到数组索引,然后沿着链表查找。
  • 扩容
  1. 默认数组长度capacity为16,当键值对数量size>threshold(threshold=loadFactor*capacity)时,需要进行扩容操作,扩容为之前的2倍。
  2. loadFactor为什么是0.75?loadFactor小,扩容越频繁,空间消耗大,hash碰撞几率小,查找速度快;loadFactor大,扩容不频繁,空间消耗小,hash碰撞几率大,查找速度慢。因此loadFactor允许使用者在空间和时间上做权衡.
  3. Java8中,当链表长度大于建树阈值(默认为8后就转换为红黑树)

3. ConcurrentHashMap

  • 底层数据结构与HashMap相同,采用分段锁(Segment),每个分段锁维护着几个桶,多个线程可以同时访问不同分段锁上的桶,并发度为锁的个数,默认为16
  • size操作:每个segment维护一个count变量记录个数,size操作时,首先不加锁,遍历所有segment把count加起来,两次这个操作,如果结果一致,就返回,不一致就对每个segment加锁获取count
  • Java7中的Segment继承自ReentrantLock实现锁机制,Java8中使用CAS实现锁机制,CAS失败时使用synchronized

4. 其他

容器数据结构是否线程安全重点备注
Vector数组使用synchronized实现线程安全,每次扩容为之前的2倍已弃用,list实现线程安全可以使用Collections.synchronizedList(list)或使用CopyOnWriteArrayList
CopyOnWriteArrayList数组读写分离,写操作在一个复制的数组上加锁写入,写完后复制到原数组,读操作不用加锁,但会引发读不到最新数据问题使用synchronized实现锁
Stack数组继承自Vector,提供栈相关方法已弃用,可以使用Deque实现Stack,Deque<Integer> stack = new LinkedList<>()
PriorityQueue数组实现队列接口Queue,是最大堆和最小堆的实现
LinkedList双向链表(双向队列)实现了双向队列Deque接口
HashTable数组+链表synchronized实现全局锁,开销较大已弃用,被ConcurrentHashMap替代
LinkedHashMapHashMap+双向链表维护了插入顺序,遍历时按照插入顺序遍历可以用来实现LRU缓存,见算法总结
TreeMap红黑树可以实现自定义排序,决定插入位置注意查找时间为O(logn)比HashMap慢
HashSetHashMapHashSet中的每个元素被包裹成Entry放入HashMap中,全部元素共用一个值

JVM

1. Java内存模型

【JVM】(1)Java内存区域

  1. 介绍一下Java内存区域以及各块区域存储的内容。
    Java内存分为运行时内存和直接内存,运行时内存分为线程共享内存和线程私有内存。线程共享内存是堆内存,主要存放对象的实例,是垃圾回收器工作的区域,在Java8之前方法区位于堆中,方法区存放常量和静态变量等信息,Java8之后,方法区称为元空间放入直接内存。线程私有内存包括虚拟机栈、本地方法栈和程序计数器,虚拟机栈存放栈帧,栈帧中存放局部变量表,本地方法栈执行Native方法,程序计数器存放下一条字节码指令。

2. 垃圾收集

【JVM】(2)垃圾收集

  1. 垃圾回收器如何判断对象没有引用?
    两种方法:引用计数法和可达性分析算法。引用分析法给对象添加一个引用计数,当存在一个引用时,计数加1,当计数为0时,就需要被回收,这种方法不能解决循环引用问题。可达性分析算法以CG Root为起点进行遍历,没有遍历到的对象就是需要被回收的,JVM采用这种方法。
  2. 有哪些引用类型?
    强引用,指针为空时,被回收;软引用,发生OOM时,被回收;弱引用,GC时被回收;虚引用,无法得到对象实例。
  3. 堆为什么分为新生代和老年代?
    对象首先在新生代分配,然后经过多次GC,没有被回收的对象逐渐进入老年代。划分区域后,可以指定回收的区域,比如MinorGC回收新生代,速度较快,性价比更高,FullGC回收整堆,速度较慢,性价比较低。
  4. 有哪些垃圾收集算法?
    标记-清除算法:先标记需要回收的对象,再清除,会造成碎片
    标记-复制算法:将内存分为两块,只使用其中一块,先标记需要回收的对象,然后复制到空白区域,再将另一块全部清空,浪费一半的内存
    标记-整理算法:将存活的对象移动到内存的一端,然后清除掉边界以外的内存。耗时。
  5. 介绍一下G1收集器
    G1把Java堆划分为多个大小相等的独立区域(Region),每个Region都可以扮演新生代的Eden空间、Survivor空间、老年代空间。经过初始标记,并发标记,最终标记,筛选回收四个阶段。优点是:不会产生碎片,以及可以指定最大停顿时间,在这个停顿时间内获取最大的收益。
  6. 什么时候出现Full GC?
    调用System.gc()(不一定出现),老年代空间不足。

3. 类文件结构与加载机制

  1. 类加载的过程是怎样的?
    首先读取class文件,然后验证文件正确性,然后将static变量赋0值,准备常量值,最后初始化static变量和static代码块。
  2. 类什么时候开始初始化?
    只有使用到这个类的时候才开始初始化,比如new 对象,访问static变量和方法,反射调用等
  3. 有哪些类加载器?
    启动类加载器、扩展类加载器、应用程序类加载器。
  4. 介绍一下双亲委派模型?
    类加载的时候,首先会把该请求委派该父类加载器的 LoadCLass()处理,因此所有的请求最终都应该传送到顶层的启动类加载器Bootstrap CLassLoader中当父类加载器无处理时,才由自己来处理。这反映了一种优先层级关系。

4. JVM性能监控、调优、实践

  • 常用JVM参数
参数功能
-Xms2G,-Xmx5G设置堆内存最小2G,最大5G
-XX:NewSize=256M,-XX:MaxNewSize=1024M设置新生代最小256M,最大1024M
-Xmn256M设置新生代大小固定为256M
-XX:NewRatio=1新生代与老年代内存大小比值为1
-XX:MetaspaceSize=1G,-XX:MaxMetaspace=2G元空间最小1G,最大2G
-XX:SurvivorRatio=2设置两个survivor区和eden区的比例为1:1:8
-Xss100K设置栈的大小
-XX:+UseG1GC使用G1垃圾回收器
-Xloggc:/path/to/gc.log记录gc到log文件
-XX:+printGC打印GC情况
  • 常用工具
名称功能
jps查看本地java进程
jstat运行数据
jinfo配置信息
jstack线程信息

并发

1. 多线程基础

【Java并发】(1)多线程基础

  1. 线程与进程的区别?
    进程是系统运行程序的基本单位,线程是进程创建的更小的执行单位。一个JVM进程可以创建多个线程,共享进程中的堆和直接内存的资源,每个线程也有自己的线程私有内存区域。线程也是轻量级进程,线程切换工作时负担更小一些。
  2. 使用多线程有哪些好处与问题?
    提高并发量,提高IO效率,提升多核CPU的利用率。
    线程安全问题,上下文切换问题,死锁问题。
  3. 并发与并行的区别?
    并发:多个任务的指令交替执行,但在一个时刻只有1条指令在执行
    并行:多条指令同时执行,如多核CPU、超线程技术同时执行多条指令
  4. 线程有哪些状态?
    – 线程被构建后进入初始状态
    – 调用start()后进入运行状态
    – 如果需要进入同步代码块,但锁没有释放,就进入阻塞状态,获取到锁后进入运行状态
    – 调用wait(),进入等待状态,其他线程notify()通知后进入运行状态
    – 调用sleep(millis),wait(millis)进入超时等待状态,时间到后返回运行状态,未释放锁。
    – 执行完毕或异常,进入终止状态。

2. 线程与线程池

【Java并发】(2)使用线程和线程池

  1. 创建线程有哪些方法?
    实现Runnable接口;实现Callable接口,使用get()获取返回值;继承Thread类。
  2. Thread类的方法
方法解释
sleep(millis)休眠,让出CPU占用,不释放锁
yield()通知调度器当前线程需要让出cpu占用,但调度器也有可能忽略这个通知
start()调用Native方法start0(),启动新线程
run()直接对run()的调用不会产生新线程
interrupt()中断该线程,如果线程处于阻塞、等待、超时等待状态,会抛出InterruptedException
join()让其他线程先执行,执行完毕后再执行本线程
  1. Object类的方法
方法解释
wait()进入等待状态释放锁
notify()通知随机一个等待的线程进入运行状态
notifyAll()通知所有等待的线程进入运行状态
  1. 线程池有哪些好处?
    重复利用已经创建的线程,降低资源消耗;可以对池中的线程进行管理分配和监控。
  2. 解释一下线程池的构造方法?
    ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
参数解释
corePoolSize核心线程数
maximumPoolSize最大线程数
keepAliveTime非核心线程空闲后的存活时间
workQueue等待处理的任务队列
handler饱和处理策略
  1. 介绍线程池的工作流程
    向线程池提交任务时,首先判断当前工作线程数是否达到核心线程数,没达到就直接创建Worker线程,执行任务;否则判断等待队列是否满了,没满就加入队列,否则判断当前线程数是否已经达到最大线程数,没达到就创建Worker线程执行任务,否则执行饱和策略。
  2. 饱和策略有哪几种?
    4种:抛出异常、在调用线程执行、忽略、丢弃队列头部的任务。另外还可以自定义策略。
  3. 为什么最好不使用Executors创建线程池?
    FixedThreadPool和SingleThreadExecutor使用无界队列,CachedThreadPool允许最大线程为Integer.MAX_VALUE,可能会造成OOM
  4. ScheduledThreadPoolExecutor和Timer的区别?
    都可以定期执行任务,Timer单线程,ScheduledThreadPool可以创建多个线程。
  5. 怎样确认线程池的大小?
    线程池大小与CPU核心数和任务性质有关,CPU密集型任务,线程池大小为N+1,IO密集型任务,线程池大小为2N。

3. 线程安全

  1. 什么是线程安全?线程安全的需要满足的三大特性?
    – 线程安全就是在多线程情况下,对共享内存的使用,不会因为不同线程的访问和修改而发生不期望的情况。
    原子性:一个或多个指令要么全部执行要么不执行,不会被其他线程中断。
    可见性:一个线程修改了共享变量,其他线程会丢弃旧数据,读取新数据。
    有序性:一个线程内部的指令重排列不会对另一个线程造成影响。
  2. 有哪些实现线程安全的方式?
    不可变。不可变的对象,如final基本类型,String,不可变集合等,在所有线程的读取中都是一致的。
    互斥同步。synchronized和ReentrantLock加锁实现。
    非阻塞同步。使用CAS乐观锁。如Atomic原子类。
    无同步方案。栈封闭,局部变量在栈帧中,不共享,不存在线程安全问题;可重入代码,不访问堆上的共享数据;线程本地存储ThreadLocal。
  3. 谈一下对于synchronized关键字的了解。
    synchronized控制多线程对于共享资源的互斥访问,保证被它修饰的方法或代码块在同一时刻只有1个线程执行,如果1个线程需要进入同步方法或同步代码块需要先获取对象的锁。
  4. synchronized在项目中有哪些使用?
    比如单例模式
  5. synchronized有哪些使用方式?
    修饰实例方法,需要获取实例对象的锁;修饰静态方法,需要获取class对象的锁;修饰代码块,需要获取指定对象的锁。
  6. JDK1.6之后,JVM对synchronized关键字做了哪些优化?
优化解释
自旋锁线程没有获取到锁时,不进入阻塞状态,而是进入忙循环一段时间,直到获取锁
锁消除对检测出来不可能存在多线程竞争的共享数据进行锁的消除
锁粗化如果一系列操作需要反复加锁解锁,那么可以将加锁解锁的范围扩大,减少重复操作
轻量级锁
偏向锁
  1. 谈一下对于volatile关键字的了解。
    volatile可以满足轻量级的线程安全,保证可见性和有序性,不保证原子性。
    volatile通过禁止指令重排列实现有序性,volatile前面的指令一定先于后面的指令执行,但前后的指令内部可能会重排列。
    volatile通过内存屏障实现可见性,volatile变量的修改会立即更新到主存,读取会重新从主存中读取。
  2. 谈一下对于ThreadLocal的理解。
    Java ThreadLocal的演化、实现和场景(转)
    ThreadLocal是线程本地变量,多线程访问共享数据的时候,可以将共享数据的副本放入每个线程的私有变量中,每个线程通过ThreadLocal实例读取到的其实是自己线程内部的ThreadLocalMap成员变量中的数据,这也是线程安全的一种解决方案,没有共享变量就不需要进行线程控制,也就没有线程安全问题。
  3. 举例说明ThreadLocal类的使用场景。
    比如SimpleDateFormat,这是线程不安全的。想实现线程安全,有3种方法 : 1. 加锁 ; 2. 每个线程内部创建一个SimpleDateFormat实例;3. 使用ThreadLocal。加锁需要线程控制,有时间开销;二三两种方法实际上是等价的,用空间换时间。但使用ThreadLocal可以简化对象的创建以及相关功能的封装。
public class DateUtil {
    private static final ThreadLocal<SimpleDateFormat> format = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    // 封装方法...
    public static String formatDate(Date date) {
        return format.get().format(date);
    }
}
  1. 谈一下ThreadLocal的get和set方法的流程。
    当调用get()或set()时,ThreadLocal对象会首先得到当前线程的ThreadLocalMap对象,这是一个哈希表,然后将ThreadLocal作为key,从中查找。解决Hash冲突的方法为线性探测法。
  2. ThreadLocal会产生什么问题?
    面试:为了进阿里,死磕了ThreadLocal内存泄露原因
    内存泄露。
    – 由于ThreadLocalMap中的key对于ThreadLocal是弱引用,如果外部没有对ThreadLocal的强引用,那么ThreadLocal会被GC掉,这样ThreadLocalMap中的key为空,value就不能被回收掉了。解决方案是将ThreadLocal用private static修饰,这样外部始终存在强引用。
    – 如果key使用强引用,那么外部没有对ThreadLocal的强引用时,ThreadLocal和Entry都不会被回收掉,也会造成内存泄露。解决方案是先remove掉ThreadLocalMap中的键值对,然后断开强引用。

4. JUC框架

  1. 介绍一下Atomic原子类。
    并发的核心:CAS 是什么?Java8是如何优化 CAS 的?
    Atomic原子类使用volatile和CAS来保证类中的每个方法都是线程安全的,比如AtomicInteger中的Integer使用volatile修饰,可以保证有序性和可见性,其中的每个方法使用CAS乐观锁保证操作的原子性。
    CAS是乐观锁的一种实现,首先读取值,然后通过一条原子的CAS指令将值设定为新值,这条CAS指令首先比较目标值是否为读取的值,是的话就设置为新值,不是的话就继续读取值,重复这个过程,直到设置成功。
  2. CAS会产生什么问题?
    ABA问题,如果一个线程读取值后,另一个线程将值修改后又改回了原值,这样之前那个线程的CAS操作是成功的,因为值没有变化。解决这个问题可以使用AtomicStampedReference进行版本控制。
  3. 谈一下关于AQS的理解。
    我画了35张图就是为了让你深入 AQS
    AQS是抽象队列同步器,是一个抽象类,JUC包中的很多类都是基于AQS来实现的,AQS中维护了一个state变量和一个线程等待队列。state代表共享资源状态,state=0表示资源空闲,state不为0表示资源被占用。当线程需要获取锁时,首先通过CAS操作将state置为1,成功后就获取到锁,失败后就加入等待队列。当线程释放锁时,会通知唤醒队列中的线程,这些线程继续通过CAS操作获取锁。
  4. JUC框架的使用
    【Java并发】(4)JUC框架
解释主要方法
ReentrantLock可重入锁,控制同步代码块访问,可以实现公平锁和非公平锁加锁lock(),解锁unlock()
Condition监视器,可以有多个监视器,可以唤醒指定的1个或多个线程,每个监视器有1条等待队列,存放等待的线程等待await(),唤醒signal(),唤醒全部signalAll()
CountDownLatch一个或多个线程等待其他线程完成操作,类似join(),比join()控制粒度更高,join需要等待其他线程执行完毕,latch只需要监控计数器为0await():阻塞线程,等待计数器归0;countDown()计数器减1
CyclicBarrier一组线程到达屏障时被阻塞,直到最后一个线程到达屏障时,所有线程都被唤醒await():表示线程到达屏障,reset():重置,后可以继续使用
SemaphoreSemaphore是流量控制工具,控制某一代码块同时可以被多少线程同时访问,比线程池的控制更精细化acquire()获取访问权,release()释放访问权
Exchanger线程数据交换exchange():第一个线程阻塞,第二个线程调用时交换
  1. CountDownLatch比join好在哪?
    CountDownLatch与join方法功能类似,但比join方法控制能力更高,join只能等待其他线程完成其全部工作后,才能返回。而CountDownLatch的计数器只监控数值是否为0,也就是说,该数值与线程数无关,一个线程可以调用countDown()方法多次。例如一个线程包含多个任务,我们只需要等待其完成一个任务即可,使用join只能等待该线程完成全部任务。
  2. Semaphore和线程池有什么区别?
    线程池控制的是线程级别的同时并发数量,而Semaphore控制的是代码块级别的同时并发数量,细粒度更高,如一个线程可能同时需要进行数据库操作和文件操作等,就需要Semaphore来精细控制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值