多线程
总结一下多线程高频面试题。
1.什么是线程?线程和进程的区别?
线程:线程是CPU调度的最小单位,也是程序执行的最小单位。没有单独地址空间,线程属于进程,不能独立执行,每个进程至少要有一个线程,称为主线程。
进程:进程是系统进行资源分配的基本单位,有独立的内存地址空间;
2.描述CPU和多线程的关系
-
第一阶段,单CPU时代,单CPU在同一时间点,只能执行单一线程。
-
第二阶段,单CPU多任务阶段,计算机在同一时间点,并行执行多个线程。但这并非真正意义上的同时执行,而是多个任务共享一个CPU,操作系统协调CPU在某个时间点,执行某个线程,因为CPU在线程之间切换比较快,就好像多个任务在同时运行。
-
第三阶段,多CPU多任务阶段,真正实现的,在同一时间点运行多个线程。具体到哪个线程在哪个CPU执行,这就跟操作系统和CPU本身的设计有关了。
3.什么是线程安全/线程不安全?
线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
4.描述下线程的生命周期?
(图很重要,很多公司笔试都会要求画出生命周期图)
-
初始状态 : 线程对象创建完成 就是new创建了一个线程对象
-
就绪状态 : 线程可以被执行 就是调用了start()方法后的状态
-
运行状态 : 线程正在运行中 就是就绪状态的线程获取了CPU执行权执行线程
-
阻塞状态 : 线程休眠 就是调用了Thread.sleep()方法使线程进入休眠
-
等待队列 : 线程陷入无限的等待中 就是线程执行wait()方法,进入这个状态后
-
锁池状态 : 线程被唤醒,但没有获取到锁 同步锁被别的线程占用,则该线程放入“锁池”中,进入锁池状态
-
死亡状态 : 线程执行完毕或被关闭 就是run()方法执行完毕后,线程就进入死亡状态
5.wait、sleep、join、yield的区别
wait:Object类的方法(notify()、notifyAll() 也是Object对象),必须放在循环体和同步代码块中,执行该方法的线程会释放锁,进入线程等待池中等待被再次唤醒(notify随机唤醒,notifyAll全部唤醒,线程结束自动唤醒)即放入锁池中竞争同步锁
sleep:Thread类的方法,必须带一个时间参数。会让当前线程休眠进入阻塞状态并释放CPU(Sleep释放CPU,wait 也会释放cpu,因为cpu资源太宝贵了,只有在线程running的时候,才会获取cpu片段),提供其他线程运行的机会且不考虑优先级,但如果有同步锁则sleep不会释放锁即其他线程无法获得同步锁 可通过调用interrupt()方法来唤醒休眠线程。
yield:让出CPU调度,Thread类的方法, yield()只是使当前线程重新回到就绪状态,所以执行yield()的线程有可能在进入到就绪状态后马上又被执行。调用yield方法只是一个建议,告诉线程调度器我的工作已经做的差不多了,可以让别的相同优先级的线程使用CPU了,没有任何机制保证采纳。
join:一种特殊的wait,当前运行线程调用另一个线程的join方法,当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。 注意该方法也需要捕捉异常。
6.Synchronized和Lock的区别
-
synchronized关键字主要解决多线程通信时候共享数据同步问题。
-
Synchronized底层使用指令码方式来控制锁的。Lock:底层是CAS乐观锁
-
Synchronized是关键字,内置语言实现,Lock是接口。
-
Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
7.ThreadLocal、Volatile、Synchronized 的作用和区别
-
ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
-
volatile用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
-
Synchronized 是Java利用锁的机制自动实现的,一般有同步方法和同步代码块两种使用方式。关键字保证了数据读写一致和可见性等问题
ThreadLocal与Synchronized区别 ThreadLocal和Synchonized都用于解决多线程并发访问,他们两者的区别:
synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问,而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。一句话来说,Synchronized是为了让多线程进行数据共享,而ThreadLocal为了让多线程进行数据隔离。
8.同步方法和同步块,哪个更好?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
9.什么是死锁?如何避免死锁?
当两个或多个线程同时运行并且拥有对方所需要的资源时,就会等待对方释放所持有的资源,但是都不会释放资源,这样就会造成堵塞,最后形成死锁。
如何避免线程死锁:
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件:一次性申请所有的资源。
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
10.线程池的作用是什么,常见的参数有哪些?
线程池的作用:
-
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
-
第三:提高线程的可管理性。
常见参数:
-
corePoolSize:核心线程数
-
queueCapacity:任务队列容量(阻塞队列)
-
maxPoolSize:最大线程数
-
keepAliveTime:线程空闲时间
-
allowCoreThreadTimeout:允许核心线程超时