面经总结:多线程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面经总结:多线程相关的知识,希望对你有一定的参考价值。
-
多线程的实现?
三种方法:1.继承Thread类;2.实现Runnable接口;3.使用Executor创建线程池;
-
多线程的的同步/线程安全的方式?
(1)同步方法:synchronized修饰的方法;
(2)同步代码块:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
同步方法和同步代码块的区别是什么?
答:同步方法默认用this或者当前类class对象作为锁; 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法。
(3)使用volatile实现同步:每次线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
(4)使用重入锁实现线程同步:ReentrantLock是concurrent包的类;常用方法有lock()和unlock();可以创建公平锁;支持非阻塞的tryLock(可超时);需要手动释放锁。
(5)使用ThreadLocal实现线程同步:每个线程都创建一个变量副本,修改副本不会影响其他线程的副本。ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量)。
-
多线程的优化?
影响多线程性能的问题:死锁、过多串行化、过多锁竞争等;
预防和处理死锁的方法:
1)尽量不要在释放锁之前竞争其他锁;一般可以通过细化同步方法来实现;
2)顺序索取锁资源;
3)尝试定时锁tryLock();
降低锁竞争方法:
1)缩小锁的范围,减小锁的粒度;
2)使用读写分离锁ReadWriteLock来替换独占锁:来实现读-读并发,读-写串行,写-写串行的特性。这种方式更进一步提高了可并发性,因为有些场景大部分是读操作,因此没必要串行工作。
-
线程池有哪几种?有哪些参数?
(1.创建;2.四类线程池;3.参数;)
创建:线程池的顶级接口是Executor,是执行线程的工具;真正的线程接口是ExecutorService;
ThreadPoolExecutor是ExecutorService的默认实现;
示例:
ExecutorService pool=Executors.newFixedThreadPool(2);
四类线程池:
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
线程池ThreadPoolExecutor的参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize-池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
关于corePoolSize和maximumPoolSize:
如果线程数<corePoolSize,有任务时,无论是否有空闲线程,都会创建新线程;
如果corePoolSize<线程数<maximumPoolSize,大于的部分放到任务队列,直到队列满了, 才会创建小于等于maximumPoolSize的线程。
-
线程的状态有哪些?
根据java.lang.Thread.State类,线程包括六个状态:
(NEW、RUNNABLE、BLOCKED、WAITTING、TIME_WAITTING、TERMINATED)
NEW:线程实例化还未执行start();
RUNNABLE:线程已经在JVM执行。(但还在等待cpu资源?存疑。似乎Running状态是包含在Runnable;Runnable就是线程执行的状态)
BLOCKED:线程阻塞;等待锁,
WAITTING:线程等待;调用Object.wait() 没有timeout 或者 Thread.join() 没有timeout 时进入该状态;
TIMED_WAITTING:线程等待;调用Thread.sleep、Object.wait(timeout) 或者 Thread.join(timeout)是进入该状态;
TERMINATED:线程终止;
(相比较原版本,DEAD更改为TERMINATED,没有Running状态,有WAITTING和TIMED_WAITTING状态;)
状态变化:
(和原版本的区别,没有Running状态;等待不属于阻塞、sleep/join不属于阻塞!)
Runnable:t.start();从new变为Runnable;
Waitting:Runnable执行wait()/join();无限等待唤醒;
Timed_Waitting:Runnable执行sleep()/wait(timeout)/join(timeout); wait释放锁,sleep()不释放锁;等待唤醒,但设置了时限;
Blocked:线程在等待锁;
-
wait和sleep区别?
-
volatile的作用?
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
使用volatile关键字修饰变量,线程要访问变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。
-
synchronized和lock的区别?
tryLock(),避免死锁;synchronized是不公平锁,而Lock可以指定锁公平还是不公平;
syn实现wait/notify机制通知的线程是随机的,Lock可以有选择性的通知。
-
synchronized和volatile的内存可见性和原子性?
原子性:即不可再分了,不能分为多步操作。比如赋值或者return。比如"a = 1;"和 "return a;"这样的操作都具有原子性。类似"a += b"这样的操作不具有原子性,在某些JVM中"a += b"可能要经过这样三个步骤:
① 取出a和b
② 计算a+b
③ 将计算结果写入内存
Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性。
-
读写锁的优势?
ReentrantReadWriteLock:读写各用一把锁;对于读多写少场景效率高。
读写锁表示两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。我把这两个操作理解为三句话:
1、读和读之间不互斥,因为读操作不会有线程安全问题
2、写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题
3、读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题
总结起来就是,多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
-
ThreadLocal的作用?
-
Java内存模型?
PS:JVM内存模型和JMM(Java内存模型)没有关系。JMM的目的是为了解决Java多线程对共享数据的读写一致性问题。
-
Concurren下的类?
以上是关于面经总结:多线程的主要内容,如果未能解决你的问题,请参考以下文章
2022年4月最新面经答案总结(Java基础数据库JVM计网计操集合多线程Spring)持续更新
《重学Java高并发》专栏开始连载:你是否懂Java高并发,一测便知(从实际应用场景中提炼多线程,理解多线程,不是面经,更胜面经)