多线程
1.什么是死锁?如何预防?
- 死锁的四个必要条件:
1.互斥条件,同一时间只能有一个线程获得资源。
2.不可剥夺,一个线程已经占有资源,释放之前不会被其他线程抢占。
3.请求和保持,线程等待过程中不会释放已占有的资源。
4.循环等待,多个线程互相等待对方释放资源。
2.线程安全问题的活跃状态?
1.饥饿:
读写锁,多个线程读的情况下写线程会阻塞,会造成写线程一直饥饿等待。
2.活锁:
有时线程没有发生阻塞的情况下,任然会存在执行不下去的情况,活锁不会阻塞线程,线程会一直重复某个相同的操作,并一直失败重试。
如开发中使用的异步消息队列可能会造成活锁的问题,在消息队列的消费端如果没有正确的ack消息,并在执行过程中报错了,就会放回消息头,再次拿出来执行,一直失败的循环往复,这个问题除了正确的ack外,还可以将失败的消息放到延时队列中,等待一定的时间失败重试。
3.sleep和wait的区别?
1.所属类:sleep是Thread的方法,wait是Object的方法,sleep是针对线程的,理应在Thread类里,wait在lock里面,锁相关,对象头相关,所以在Object里面。
2.释放锁,wait涉及到锁相关,所以会释放锁,sleep是针对线程相关,不涉及到锁,所以不会释放锁。
4.线程与进程的区别?
1.进程是系统进行分配和调度的最小单位,进程是指在系统中正在运行的恶一个应用程序,程序一旦运行就是进程。
2.线程是操作系统调度的最小单位。
- 当a是一个进程,当a fork可一个进程和他共享资源时,a和b成为线程。
5.java线程的生命周期?
1.大致包含五个状态:
- 新建状态,new 出来的线程;
- 就绪状态,调用线程的start方法,线程会等待cpu分配资源的阶段,谁先抢到cpu的资源,谁开始执行;
- 运行状态,当前线程被调度并获得cpu的资源,进入运行状态。
- 阻塞状态,在线程运行的时候,由于某些原因导致线程变为阻塞状态,如调用sleep(),wait()方法等等,这时候需要其他机制将当前线程唤醒,如notify(),notifyall()等等,被唤醒的线程再次变为就绪状态,等待cpu的调度再次变为运行状态。
- 销毁状态。
6.程序开多少线程合适?
1.区别应用是什么样的线程:
7.notify和notifyAll区别?
- 锁池和等待池的概念?
1.锁池:当线程A获得某个对象的锁,而其他线程想要调用这个对象的synchronize方法获取该对象的锁,由于A线程已经持有当前对象的锁,其他尝试获取锁的线程会进入对象的锁池。
2.等待池:线程A调用了对象的wait方法,线程A就会释放当前线程的锁,因为wait方法必须出现在synchronize同步方法或代码块中,因此调用wait之前已经持有了当前对象的锁,wait释放锁之后线程会进入改对象的等待池中,如果另外一个线程调用该对象的notifyAll方法时,那么处于等待池中的线程会全部进入到锁池中,准备争夺锁的拥有权,如果另外一个线程调用了notify方法,边会有一个线程进入对象的锁池中。
1.如果调用的对象的wait方法,那么线程便会处于对象的等待池中,等待池中的对象不会去竞争锁。
2.当线程调用对象的notifyAll方法(唤醒所有线程)或者notify方法(唤醒一个线程),唤醒的线程便会进入对象的锁池中去竞争锁的拥有权。
8.synchronize和lock的区别?
-
sync是java的一个关键字,jvm级别,lock是一个接口。
-
sync会自动释放锁,lock需要在finally块中手动释放。
-
lock提供了更多的方法,可以相应中断,而sync不行。
uploading-image-77364.png -
sync是非公平锁,不能保证等待锁的执行顺序,而lock默认是非公平锁,可以传入一个构造参数变为公平锁。
-
sync无法判断是否已经获取到锁,而lock可以通过tryLock判断。
-
底层实现不一样,sync是同步阻塞的悲观并发策略,lock是同步非阻塞(基于volatile和cas实现)。
9.多线程之间如何通信?
- 根据操作系统理论:多进程之间的通讯是通过:信号量,信号,套接字,有名管道,无名管道,共享内存,mq等来实现的,由于多线程之间共享进程的地址空间,所以原生就支持数据共享,当一个线程修改了进程的变量,另一个线程自然就能看到,所以原生就支持通讯,由于线程之间的并发会引起互斥操作,所以就需要同步机制:volatile,sync,ReentrantLock,ReadWriteLock等。
具体实现:
1.通过共享变量,volatile来实现。
2.使用wait()和notify()方法来实现,但是由于使用同一把锁,所以必须通知线程释放锁,另一个线程才能获取到锁,这样导致通知不及时。
3.CountDownLatch实现,通知线程达到指定条件,调用countDownLatch.countDown(),被通知的线程调用countDownLatch.await()。
4.使用Condition的await()和signAll();
10.sync的底层实现?以及locak的区别?
11.synchronized修饰静态方法和实例方法的区别?
1.修饰静态方法,是对类加锁
2.修饰实例方法,是对实例进行加锁,锁的是实例对象的对象头。
12.countDownLatch的用法?
1.让主线程await,业务线程去处理逻辑,处理完成调用countDownLatch.countDown(),CountDownLatch实例化时需要根据业务去选择CountDownLatch的count;
2.让业务线程await,主线程处理完数据后调用CountDownLatch.countDown(),此时业务线程被唤醒,处理数据。
13.线程池问题
-
Executor提供了几种线程池?
-
线程池参数
-
拒绝策略
1.由调用线程处理改任务。
2.丢弃任务,抛出异常。
3.丢弃任务,不抛出异常。
4.抛弃进入队列最早的那个任务。 -
任务放置的顺序过程
-
任务结束后会不会回收线程?
如果是非核心线程,等待一段时间没有拿到任务,就会超时回收,如果指定允许核心线程睡眠(allowCoreThreadTimeOut),一段时间后就会睡眠,线程在worker的一个HashSet里面,由于HashSet是非线程安全的,所以需要ReentrantLock锁住
14.线程池的生命周期几种状态
1.running:接受新提交的任务,并且也能处理阻塞队列中的任务。
2.shutdown:关闭转台,不接受新提交的任务,但是可以继续处理阻塞队列中的任务。
3.stop:不接受新任务,也不处理队列中的任务,会中断正在处理的线程。
4.tidying:所有任务已经终止,workcount=0
5.terminated:在terminated()方法执行后进入此状态。
15.如何在方法栈中传递数据?
1.通过方法参数。
2.通过共享变量。
3.如果咋同一个线程中,ThradLocal传递。
16.ThreadLocalMap
对于第二种情况,在调用get时会自动清理掉那些key为null的
17.锁的四种状态以及升级过程?
- synchronized锁升级过程
1.如果是偏向锁:
先检查当前对象中的mark word中的记录是否是当前线程id,如果是,则获取偏向锁执行同步代码块。如果不是,则通过cas操作替换线程id,替换成功获得偏向锁,失败则撤销替换升级轻量级锁。
2.升级轻量级锁:
参考:
https://www.cnblogs.com/shan333/p/16736163.html
18.ThreadLocal相关
-
ThreadLocal的理解:
ThreadLocal提供了线程的本地变量,保证访问的变量属于当前线程,每个线程都有一个变量副本,线程之间是隔离的。适用于在多线程下可以实现传递数据,实现线程隔离。 -
ThreadLocal应用场景:
1.Spring事务模板类。
2.SpringMvc获取httprequest,将httpRequest对象缓存到当前线程中。 -
ThreadLocal底层原理:
1.每个线程都有自己独立的ThreadLocalMap对象。
2.获取当前线程。
3.获取当前线程的ThreadLocalMap对象。
4.设定当前线程的ThreadlocalMap的entrty对象,key为ThreadLocal。
-
为什么线程缓存的是ThreadLocalMap对象?
ThreadLocalMap对象可以缓存多个ThreadLocal对象,每个ThreadLocal对象只能缓存一个变量值。当从ThreadLocal对象get时,会先获取当前线程的ThreadLocalMap,再从map中取出ThreadLocal对应的值。 -
强,弱,软引用之间的区别?
强引用:当内存不足时,jvm会进行gc垃圾回收,对于强引用对象,就算出现了OOM,强引用对象也不会被回收。
软引用(SoftReference):当内存充足情况下,不会被回收,当内存不足情况下,他会被回收。软引用通常会存在对内存比较敏感的系统中,如寄存器高速缓存。
(未设置虚拟机堆内存大小,软引用一直存在)
(-Xms5m-Xmx5m -XX:PrintGCDetails 设置堆内存大小)
弱引用(WeakReference):他比软引用的生存周期更短,对于弱引用的对象来说,只要有垃圾回收,不管内存空间是否充足,都会回收掉该对象占用的内存空间。
虚引用(PhantomReference):与其他几种引用不同,虚引用并不会决定对象的生命周期。
-
ThreadLocal内存泄漏问题
内存泄漏:申请的内存空间无法释放。
内存溢出:申请内存时内存不足。
由图可以看出,ThreadLocalMap的entrty的key为指向堆内存的ThreadLocal的一个弱引用,当垃圾回收时,若ThreadLocal没有其他地方强引用时,key指向的引用就会断开,被垃圾回收掉,这是就出现了ThreadLocalMap中key为null的entrty,value还通过强引用指向其他的对象,只要当前线程不结束,ThreadLocalMap对象就会一直存在,因为这时还存在一个强引用的引用链:Current Thread Reference --> Current Thread --> ThreadLocalMap --> EntryValue --> Object,这时就造成内存泄漏。 -
如何解决内存泄漏?
1.ThreadLocal使用完毕是,调用remove方法清除数据。
2.在set方法之前,会清除当前map中的key为null的entrty。
19.AQS相关
-
Lock锁底层实现:
Aqs+cas+LockSupport实现。 -
LocalSupport用法:
阻塞当前线程:LockSupport.park()
唤醒阻塞的线程:LockSupport.unpark()
-
CAS:
见上文 -
lock的实现
1.非公平锁的实现:
当执行lock操作时,会先进行cas操作尝试更新state状态,若更新失败,则会重试,如果重试多次失败,则会把当前线程阻塞,放入队列中
- AQS的实现:
1.state状态:0代表锁没有被其他线程获取,1代表锁被其他线程占有,大于1表示当前线程重入获取锁。
当多个线程通过cas操作去获取锁,此时只有一个线程更改锁状态成功,获取到锁,其他未获取到锁的线程会存放到一个链表(双向链表)里面,当T1线程执行完毕释放锁之后,通过cas操作更改锁的状态,并唤醒正在阻塞的线程重新参与到竞争锁的状态。
20.SemaPhore信号量原理
1.seamPhore可以用于控制访问某些资源的线程数目,他维护了一个许可证集合,有多少资源需要限制就要维护多少个许可证集合,假设有N个资源,就对应N个许可证,一个线程获取许可证就调用acquire方法,用完释放就调用release方法。
2.可以实现用于接口限流,底层基于AQS来实现,原理更改state状态,调用acquire方法将state减一,release方法加一。
例:
@Test
void test7() throws Exception{
Semaphore semaphore = new Semaphore(5);
for (int i=0;i<10;i++){
int finalI = i;
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+","+ finalI);
} catch (InterruptedException e) {
}
}).start();
}
21.CountDownLatch原理
它允许一个或多个线程等待直到其他线程中的一组操作完成,基于AQS来实现的。
CountDownLatch countDownLatch = new CountDownLatch(2);
AQS的state变量值为2,调用countDownLatch.countDown();state的值会减一,当AQS的state状态为0时,唤醒其他等待的线程。
@Test
void test8(){
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(()->{
try {
System.out.println("t1开始执行");
//子线程阻塞,想被唤醒调用countDown方法将aqs状态变为0
countDownLatch.await();
System.out.println("t1结束");
} catch (InterruptedException e) {
}
}).start();
countDownLatch.countDown();
countDownLatch.countDown();
}