建议:
- 学习java并发前需要先掌握JVM知识
- 关于下面问题档案的详细解析都在后面推荐的相关系列文章中
一、线程安全相关
1、什么叫线程安全?
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
2、为什么会存在线程安全的问题?
- 共享性:多个线程操作同一个类实例时,类属性是共享的。
- 互斥性:为保证数据安全性,同一时刻只有一个线程可以修改数据
3、如何保证线程安全?
- 原子性:针对代码块
- 可见性:数据从线程工作内存刷新到JVM主存中这个动作必须是同步的
- 有序性:指令重排,操作系统对指令进行重排序时只保证单线程时最终结果的正确性,不能保证多线程情况下最终结果的正确性
4、同步有几种实现方法?(或者问Java 程序中怎么保证多线程的运行安全?)
JDK内置的syncchronized关键字、共享变量(volatile)实现线程同步、JUC的可重入锁ReentrantLock类、使用局部变量实现线程同步
5、volatile关键字有什么作用?弊端是什么?适用场景是什么?
1)作用:通过volatile关键字可以保证变量的可见性和有序性,但不能保证原子性
- 可见性:变量修改被立即刷新到主存中;变量读取不能从工作内存读取,必须从主存读取。(通过硬件锁保证线程安全的)
- 有序性:volatile变量前的代码不能重排到volatile变量后,volatile变量后的代码也不能重排到volatile变量前。
2)弊端
- 原子性:i++操作是非线程安全的,即使通过volatile关键字修饰也不能保证其原子性
3)适用场景
6、volatile与synchronized区别是什么?什么场景下可以使用volatile替换synchronized?
volatile只能保证可见性和有序性,synchronized可以保证原子性、可见性、有序性
- 状态标志:把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait、notify或者interrupt的编码麻烦。
- 替换重量级锁:如果某个变量仅是单次读或者单次写操作,没有复合操作(i++,先检查后判断之类的)就可以用volatile替换synchronized。
二、CAS相关
1、CAS是什么
CAS是乐观锁思想的一种实现方式,也是无锁保证变量线程安全的一种方式。原理是将变量的新值写入主存前,需要比较工作内存中的旧值和主存中的旧值是否相等(相等说明主存中的变量没有被其他线程修改过),相等则将新值写入主存中。
2、ABA问题
比如说一个线程one从内存位置V取出value_A,这时候另一个线程two也从内存中取出value_A,并且线程two进行了一些操作变成了value_B,然后two又将V位置的数据变成value_A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。解决方法:通过版本号(version)的方式来解决,每次比较要比较数据的值和版本号两项内容即可。
3、atomic 的原理?
三、锁的种类
1、分类
- 乐观锁/悲观锁:假设每次操作都不会冲突,若是遇到冲突失败就重试直到成功;悲观锁是让其他线程都等待,等锁释放完了再竞争锁。
- 共享锁/独占锁:也叫读写锁/互斥锁
- 公平锁/非公平锁
- 可重入锁:线程可以进入任何一个它持有锁的线程
- 自旋锁
- 自适应自旋锁
- 无锁/偏向锁/轻量级锁/重量级锁
2、乐观锁/悲观锁实现方式?
- 乐观锁实现方式:cas,volatile
- 悲观锁实现方式:synchronized,Lock
3、读写锁可以用于什么场景?
适用于少写多读的情况,互斥锁效率低,而多线程都是读的情况不需要互斥,只有读-写、写-写冲突时才需要互斥,通过读写锁可以提高效率,具体工具类是ReentrantReadWriteLock
4、什么时候应该使用可重入锁?
可重入锁指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,ReentrantLock 和synchronized都是可重入锁,所以大部分情况下使用的都是可重入锁
5、锁的4种状态以及膨胀过程
膨胀方向:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(只能单向膨胀,不能逆膨胀)
膨胀原因:多线程竞争加剧,导致锁升级
6、什么是死锁? 怎么防止死锁?
三、Synchoronied
1、Synchoronied是什么?
关键词:重量级锁、悲观锁、互斥锁、
2、使用方法
用于修饰普通函数、静态函数、代码块
3、底层原理:
通过底层cpu的monitorenter和monitorexit对线程的出入进行控制
4、优化:
轻量级锁、偏向锁
5、当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?
- 如果其他方法前加了synchronized关键字,就不能,如果没加synchronized,则能够进去。因为一个对象只有一个monitor锁,当一个线程进入synchronized的实例方法后便获得了当前对象的monitor锁,其他线程由于无法获得monitor锁,所以无法进入此对象的其它被synchronized修饰的方法。
- 如果这个方法内部调用了wait(),则释放了monitor锁,则可以进入其他加synchronized的方法。如果其他方法加了synchronized关键字,并且没有调用wai方法,则不能。
四、Lock类
1、底层原理:
依赖于AQS
2、AQS原理?
关键词:juc包的Lock类的基石
数据结构:带有头尾指针的双向链表,节点Node中包含头指针、尾指针、抢占状态3个重要的属性
作用:实现线程的抢锁、释放锁、排队等待、阻塞唤醒等操作
3、Lock锁、ReentrantLock、ReentrantWriteReadLock、LockSupport?
4、CountDownLatch、CyclicBarrier 、Semaphore?
5、wait/notify()/notifyAll()、await()/signal/signalAll区别?
6、synchronized和java.util.concurrent.locks.Lock的异同?
- 主要相同点:Lock能完成Synchronized所实现的所有功能。
- 主要不同点:
五、多线程操作相关
1、并行和并发有什么区别?
2、线程和进程的区别?
3、 守护线程是什么?
4、创建线程有哪几种方式?
5、说一下 runnable 和 callable 有什么区别?
6、线程有哪些状态?
7、 sleep() 和 wait() 有什么区别?
8、notify()和 notifyAll()有什么区别?
9、 线程的 run() 和 start() 有什么区别?
10、创建线程池有哪几种方式?
11、线程池都有哪些状态?
12、线程池中 submit() 和 execute() 方法有什么区别?
13. ThreadLocal 是什么?有哪些使用场景?
六、并发编程实战
1、如何实现一个流控程序,用于控制请求的调用次数?
答案参考:Java并发编程—实战
Java并发编程系列文章:
- Java 并发编程—核心理论
- Java并发编程—volatile关键字(保证变量的可见性、有序性机制)
- Java并发编程—无锁互斥机制及CAS原理
- Java并发编程—锁的基本概念
- Java 并发编程—Synchronized关键字
- Java并发编程—Synchronized底层优化(偏向锁、轻量级锁)
- Java 并发编程—有锁互斥机制及AQS理论
- Java并发编程—AQS原理分析
- Java并发编程—自旋锁CLHLock、MCSLock原理
- Java并发编程—JUC的Lock锁
- Java并发编程—线程同步类
- Java并发编程—线程间协作方式wait()、notify()、notifyAll()和Condition
- Java并发编程—为什么 wait() 方法需要写在 while 里,而不是 if?