Java多线程面试题①
说点什么
小编最近通过面试发现,几乎每个面试官都热衷于问多线程方面的知识点,不管是出于项目的需求,还是作为Java基础,多线程是每个程序员应该要掌握的东西。小编整理了一些多线程方面的面试题,一方面是记录下常见的面试问题,也希望对正在面试的你有所帮助吧。
废话不多说了,开始看题吧。
进入正题
1、进程和线程的区别?
- 进程是系统资源分配的基本单位,线程是CPU分配资源的基本单位。
- 进程间是相互独立的,同一个进程内的多个线程是可以共享的。
- 进程切换比线程切换对系统的开销大,线程切换得快。
注意:进程开销大线程开销小的原因:每个进程都有独立的代码和数据空间(即程序上下文);而同一类线程共享代码和数据空间(即程序上下文),每个线程都有独立的运行栈和程序计数器。
2、线程创建的几种方式?
线程创建有以下4种方式:
- 继承Thread类。(重写run(),创建子类的实例,通过start()启动该线程)
- 实现Runnable接口。(重写run(),创建实现类的实例作为参数,创建Thread对象,通过start()启动线程)
- 实现callable接口。(重写call()并有返回值可抛异常,创建实现类的实例作为FutureTask类的包装对象,使用FutureTask对象作为参数创建Thread对象,通过start()启动线程,通过FutureTask对象的get()可获取子线程德返回值)
- 使用通过Executor提供的线程池。
3、线程池的种类有哪些?运行流程是怎样的?使用线程池有何优势?
线程池的种类有4种:
- 单线程化的线程池(newSingleThreadExecutor ):保证池中只有一个线程执行任务。
- 定长的线程池(newFixedThreadPool ):创建固定数目线程,超出的线程会在队列等待。
- 调度型的线程池(newScheduledThreadPool ):指定调度规则,可定时和周期性的执行。
- 可缓存的线程池(newCachedThreadPool):超过长度,先判断有无空闲的线程,有则回收,无则新建。
线程池的运行流程如下图:
线程池的优点:
- 降低了资源的消耗
- 提高响应速度
- 便于管理,高效使用
4、简要说一下线程的基本状态及其状态间的关系?
如下图所示:
5、线程同步和调度的方法有哪些?说一下各自的功能和区别?
Thread类:
- start():启动新创建的线程。
- run():执行任务代码。
- sleep():使正在运行的线程休眠,不会释放锁。
- yield():放弃CPU的使用权,退出运行状态,进入可运行状态。
- join():让后面的某个线程在当前线程前面先执行,再执行当前线程。
Object类:
- wait():使当前运行的线程进入阻塞状态,会释放对象锁,会抛异常。
- notify():唤醒处于阻塞状态的线程,但不确定是哪个,由JVM决定。
- notifyAll():唤醒所有处于阻塞状态的线程,让它们竞争,只有获取锁的线程才能进入可运行状态。
Lock类:
- lock():获取锁(互斥锁)。
- unlock():释放锁。
- tryLock():判断是否获取了锁。
- newCondition():创建与Lock对象关联的Condition对象。
Condition类:
- await():同wait()。
- signal():同notify()。
- signalAll():同notifyAll()。
6、同步方法和同步代码块的区别?
为什么要同步?
Java允许多线程并发控制,多个线程操作同一个资源变量时,会出现数据不准确,互相冲突等现象。加入同步锁可以避免线程不安全的情况,保证变量的准确性和唯一性。
语法上:
同步方法是在方法上加入synchronized关键字,而同步代码块是synchronized(){}。
锁使用:
同步方法用this(非静态方法)或当前类的class对象(静态方法)作为锁,同步代码块的粒度比同步方法更细。
7、synchronized与Lock类的区别?
Lock类和java内置的synchronized关键字的目的都是为了解决多线程同步问题,处理资源争夺技术。而是还是有些区别的(解决synchronized无法实现的):
- synchronized无法控制加锁和释放锁,Lock类必须手动释放锁。
- Lock类可以实现并发读文件操作。
- synchronized无法知道是否获取了锁,Lock类通过tryLock()可以判断。
- 结合Condition类使用。性能更好,可以实现等待通知的功能。
ps:顺带总结下Lock经常使用的场景
- 通过ReentrantLock实现线程同步
- 通过Condition实现等待通知的功能
- 通过ReentrantReadWriteLock实现读写锁
8、死锁的必要条件是什么及怎么避免死锁?
死锁:多个线程在竞争资源的过程中出现相互等待的情况,无外力作用,无法推进下去。
死锁四个条件:
- 互斥使用
- 不可抢夺
- 保持和请求
- 循环等待
避免死锁的技术: - 顺序加锁
- 限时加锁
- 死锁检测
9、悲观锁和乐观锁的区别?
悲观锁:认为每次拿数据都会被修改,在操作数据之前都会加把锁。常见的数据库中的行锁,表锁,读锁等,就是操作前都会先上锁。synchronized和ReentrantLock也是基于这种思想实现的。
乐观锁:认为每次拿数据都不会被修改,不会加锁。适用于多读场景,提高吞吐量,通常使用版本号机制和cas算法实现。
10、高级多线程控制中经常用到原子类,对原子类进行一些简单的总结?
java.util.concurrent.atomic包中的类及其方法
该包有12个类,原子更新方式有4种:
- 基本类型(AtomicBoolean/AtomicLong/AtomicInteger)
- 数组(AtomicLongArray/AtomicIntegerArray/AtomicReferenceArray)
- 引用(AtomicReference/AtomicReferenceFieldUpdater/AtomicMarkableReference)
- 字段(AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicStampedReference)
注意:一般常用的AtomicInteger、AtomicReference和AtomicStampedReference。
以AtomicInteger类为例,说一下它里面有哪些常用的方法? - boolean compareAndSet(int e,int n):比较预期值与修改值,相等则设置修改值
- int addAndGet(int delta):将输入值与实例的值想加,返回结果
- int getAndIncrement():实现当前值加1,返回自增前的值
- int getAndSet(int newValue):设置新值,返回旧值
- void lazySet(int newValue):最终设置为新的值
i++问题
对于这种复合类的操作,可以使用并发包下的原子操作类通过循环CAS方式保证原子性。
cas以及ABA问题
cas:一种乐观锁,实现思路是内存值v,预期值e,修改值n,当v等于e时,说明其他线程没有修改该内存值,将n赋给v。
乐观锁不足的地方:出现ABA问题。
解决方法:使用带版本号的引用类型:AtomicStampedReference
结尾
理解多线程的面试题,需要做到循环渐进,先把基础的问题搞清楚了才是最重要的。像Thread类、Lock类、原子类等都是多线程开发中需要知道的,还有些锁相关的问题了解其设计思想和区别更不用多说了。
以上整理的内容,如有不妥或错误,敬请指正。请继续期待下一篇面试题,一起学习~