目录
面试题3.44 多线程的同步方式
面试题3.45 多线程安全问题怎么解决
面试题3.46 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
面试题3.47 简述synchronized与java.util.concurrent.locks.Lock的异同?synchronized和Lock锁两者区别? 谈谈 synchronized和ReentrantLock的区别
面试题3.48 sleep()和wait()有什么区别?
面试题3.49 线程之间是如何通信的?
面试题3.50 为什么wait(),notify()和notifyAll()必须在同步方法或者同步块中被调用?
面试题3.51 线程阻塞有几种情况?遇到阻塞怎么解决?
面试题3.52 如何安全中断运行中的线程?interrupt()方法的作用?
面试题3.53 volatile关键字的作用,能保证线程安全吗
面试题3.54 Java 中 ++ 操作符是线程安全的吗?
面试题3.55 请说说ThreadLocal?请说说线程本地变量?请说说本地线程?
面试题3.56 死锁的原因
面试题3.57 死锁与活锁的区别,死锁与饥饿的区别?
面试题3.58 java多线程有几种实现方式?你推荐哪一种
面试题3.59 线程池的优点?
面试题3.60 什么是线程池?有哪几种创建方式?四种线程池的创建方式?
面试题3.61 HashMap如何实现线程安全? ConcurrentHashMap和Hashtable的区别?
面试题3.44 多线程的同步方式
【技术难度:2 出现频率:2 】
1.synchronized修饰的同步代码块;
2.synchronized修饰的同步方法;
3.Lock锁。
面试题3.45 多线程安全问题怎么解决
【技术难度: 2 出现频率:2 】
解决思路是尽量避免多个线程同时操作相同变量。
解决方案有这些:
1.多实例,为每个线程创建一个实例,缺点是浪费空间;
2.添加synchronized关键字,缺点是效率低,逐个线程排队执行;
3.使用本地线程变量ThreadLocal;
4.使用Lock锁,缺点是效率低,逐个线程排队执行;
5.使用局部变量,因为局部变量不存在线程安全的问题,缺点是多线程时不一定具备这种场景。
面试题3.46 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
【技术难度:2 出现频率:1 】
假设这个线程进入的是该对象的synchronized实例方法,此时其它线程可以进入此对象的其它方法有未加锁的实例方法和类方法,以及synchronized加锁的类方法。
面试题3.47 简述synchronized与java.util.concurrent.locks.Lock的异同?synchronized和Lock锁两者区别? 谈谈 synchronized和ReentrantLock的区别
【技术难度: 2 出现频率:1 】
接下来以实现类ReentrantLock代表Lock锁来回答此问题:
第一层:
1.synchronized是java关键字; Lock是java接口,jdk1.5才出现;
2.synchronized会自动获取或释放锁,Lock锁需要调用lock()或unlock()方法手动获取或释放锁;
3.synchronized如果获取不到锁会一直等待,ReentrantLock如果获取不到锁可以设置时间,超过不等待;
第二层:
4.synchronized是非公平锁,ReentrantLock可以实现公平锁;
5.synchronized只能随机或者全部唤醒,ReentrantLock可以精确或分组唤醒;
6.synchronized锁适合少量代码的同步问题,Lock锁适合大量代码的同步问题。
拓展:
竞争激烈的情况下,Lock锁的性能优于synchronized。竞争不激烈的情况下,synchronized性能好一点,因为synchronized有个锁升级机制,根据竞争激烈程序给锁升级,从偏向锁到轻量级锁再到重量级锁,而到重量级锁的转换需要操作系统帮忙,需要花费较多时间。
面试题3.48 sleep()和wait()有什么区别?
【技术难度:1 出现频率: 1 】
1.sleep()是Thread类的静态方法,wait()是Object类的实例方法;
2.sleep不会释放对象锁,wait会释放对象锁;(会造成锁的二次释放吗?)
3.sleep的休眠时间到达后自动进入就绪状态,wait的线程必须由notify()或notifyAll()唤醒,唤醒后进入同步阻塞状态(重新竞争锁)。
面试题3.49 线程之间是如何通信的?
【技术难度: 2 出现频率:1 】
通过Object类的wait()、notify()、notifyAll(),以及Thread类的join()方法,可以进行消息传递,实现让线程等待或唤醒线程,
多线程之间通信是为了避免对同一共享资源的争夺。
面试题3.50 为什么wait(),notify()和notifyAll()必须在同步方法或者同步块中被调用?
【技术难度: 2 出现频率: 1 】
1.调用wait()的线程会释放锁,很显然,先获得锁才能释放锁;
2.notify()、notifyAll()是将锁交给调用了wait()方法的线程,让其继续执行下去,前提是自身获得了锁才能交出锁。
面试题3.51 线程阻塞有几种情况?遇到阻塞怎么解决?
【技术难度: 2 出现频率:2 】
线程阻塞的情况分三种:等待阻塞、同步阻塞、其他阻塞。
三种线程阻塞展开说是这样:
1.等待阻塞是指运行的线程执行了wait()方法,JVM会把该线程放入等待池中;
2.同步阻塞是指运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;
3.其他阻塞是指运行的线程执行了sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
线程阻塞的解决方法有:
1.sleep()、wait()和join()方法引发的阻塞可以通过interrupt()方法中断;
2.同步锁引发的阻塞可以通过减少锁持有时间,读写锁分离,减小锁的粒度,锁分离,锁粗化等方式来优化锁的性能。
扩展(无需背诵):
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
面试题3.52 如何安全中断运行中的线程?interrupt()方法的作用?
【技术难度: 2 出现频率: 1 】
Java Thread 的API里面虽然提供了一个 stop() 方法,可以强行终止线程,但这种方式是不安全的,因为有可能线程的任务还没有完成,突然中断会导致出现运行结果不正确的问题。
因此,在 Thread 里面提供了一个 interrupt() 方法,这个方法要配合isInterrupted()方法来使用,就可以实现安全地中断线程运行。 这种实现方法并不是强制中断,而是告诉正在运行的线程,你可以停止了。何时实际中断,取决于正在运行的线程,所以,它能够保证线程运行结果的安全性。
public class SafeInterruptExample { public static void main(String[] args) { Thread worker = new Thread(new RunnableTask()); worker.start(); // 中断工作线程 worker.interrupt(); } static class RunnableTask implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { // 执行任务 System.out.println("Thread is running..."); } } }
}
面试题3.53 volatile关键字的作用,能保证线程安全吗
【技术难度: 2 出现频率: 1 】
volatile关键字有两个作用,一是可以保证线程获取的数据是最新值(可见性),二是可以防止指令的重排序。它无法保证线程的安全性。
扩展(无需背诵):
当一个共享变量被 volatile 修饰时,它能保证修改的值会立即被更新到主内存,当有其他线程需要读取时,会去主内存中读取新值。
面试题3.54 Java 中 ++ 操作符是线程安全的吗?
【技术难度: 2 出现频率: 1 】
不是。它涉及到多个指令,如读取变量值,增加,然后存储回内存,并不是原子性的操作,这个过程可能会出现多个线程交差。
一个解决方案是对有i++操作的方法加同步锁。另一个解决方案是使用支持原子操作的类,比如AtomicInteger(java.util.concurrent.atomic.AtomicInteger)。
面试题3.55 请说说ThreadLocal?请说说线程本地变量?请说说本地线程?
【技术难度: 2 出现频率:1 】
第一层:
ThreadLocal可以实现每⼀个线程都有⾃⼰的专属本地变量,创建了⼀个ThreadLocal变量之后,访问这个变量的每个线程都会有这个变量的本地副本。他们可以使⽤get()和set()⽅法来获取或修改当前线程所存的值,从⽽避免线程安全问题。
第二层:
每个线程的本地变量并不是存放在ThreadLocal实例中,而是放在线程对象自己的threadLocals变量中,也就是说,ThreadLocal本地变量是存放在具体的线程空间上,所以不使用本地变量的时候需要调用remove方法删除不用的本地变量(不影响其他线程),否则只要线程不终止那这些不用的变量就会一直存活在线程对象中。
面试题3.56 死锁的原因
【技术难度: 1 出现频率: 1 】
当两个或两个以上的线程(或进程)在执行过程中,互相争夺对方持有的互斥资源,又不释放自己持有的锁资源,造成互相一直等待,此时若无外力作用,它们都将无法推进下去,这就是死锁。
面试题3.57 死锁与活锁的区别,死锁与饥饿的区别?
【技术难度: 3 出现频率:1 】
死锁和活锁的区别在于:处于活锁的线程是在不断的改变状态,就是所谓的“活”,而
处于死锁的线程表现为一直等待,活锁有可能自行解开,死锁则不能。
死锁与饥饿的区别在于,死锁除非外力无法解开,饥饿能够被解开,比如当其他高优先级的进程都终止时并且没有更高优先级的进程到达。
死锁、活锁、饥饿基本概念:
1.死锁:是指两个或两个以上的线程(或进程)在执行过程中,因争夺锁资源而造成
的一种互相等待的现象(卡住了),若无外力作用,它们都将无法推进下去。
2.活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,
失败,尝试,失败。
3.饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执
行的状态。比如cpu一直给你分到调度。或一直wait,
产生死锁的必要条件:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.等待循环条件:若干进程之间形成一种头尾相接的循环等待资源关系。
Java中导致饥饿的原因:
1.高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2.线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前
持续地对该同步块进行访问。
3.线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方
法),因为其他线程总是被持续地获得唤醒。
面试题3.58 java多线程有几种实现方式?你推荐哪一种
【技术难度:2 出现频率:2 】
有四种,分别是:
1.继承Thread类,重写run()方法;
2.实现Runnable接口,重写run()方法,这种最常用;
3.实现Callable接口,重写call()方法;(FutureTask对象的get()方法)
4.线程池。
推荐使用线程池 因为线程池中的线程可以循环使用。
面试题3.59 线程池的优点?
【技术难度: 2 出现频率:2 】
1.可重复使用已有线程;(池化的优点)
2.可有效控制最大并发线程数;
3.提供定时执行、定期执行、单线程、并发数控制等多种功能。
面试题3.60 什么是线程池?有哪几种创建方式?四种线程池的创建方式?
【技术难度: 2 出现频率:2 】
线程池用来管理多个线程,可以先创建好若干线程,使用时直接获取,以空间换时间的方式,提高线程的使用效率。(事先开好,直接从内存中申请,而不需要向系统申请)
线程池创建方式:
- Executors.newCachedThreadPool():创建 可以根据需要创建新线程 的线程池,不够创建,够用了回收,无限大;
- Executors.newFixedThreadPool(n); 创建可重用固定线程数的线程池,如果超过了需要等待;
- ScheduledExecutorService ses = Executors.newScheduledThreadPool(n):创建可周期定长线程池,它可延迟运行或周期执行;【延迟操作是子类自己的方法,所以不能用父类对象来引用】
- Executors.newSingleThreadExecutor() :创建只有一个线程的线程池。
或者创建自定义线程池,根据需求指定线程池参数。
面试题3.61 HashMap如何实现线程安全? ConcurrentHashMap和Hashtable的区别?
【技术难度: 2 出现频率: 1 】
第一层:
- 使用ConcurrentHashMap,它只锁住要修改的部分;
- 使用Collections类的synchronizedMap()方法包装一下,这种方式获得的线程安全的HashMap在读写数据的时候会对整个容器上锁,效率低下;
- Hashtable读写数据的时候会对整个容器上锁,效率低下。
推荐使用ConcurrentHashMap。
第二层:
ConcurrentHashMap在jdk1.7之前使用分段锁,将数据分段加不同对象锁,jdk1.8开始它用数组中每个头节点作为锁对象来使用synchronized锁,并使用CAS操作来进一步提高效率
扩展(无需背诵):
代码:
Map<Long,Object> map2 = new ConcurrentHashMap<>();
Map<Long,Object> map1 = Collections.synchronizedMap(new HashMap<Long,Object>());
Map<Long, String> map = new Hashtable<>();
------------------------END-------------------------
才疏学浅,谬误难免,欢迎各位批评指正。