java多线程面试

java多线程面试

一、线程和进程的区别?

  • 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
  • 线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

二、创建线程有哪几种方式

①. 继承Thread类
②. 实现Runnable接口
③. 通过Callable和Future创建线程
④. 通过线程池创建线程;

三、说一下 runnable 和 callable 有什么区别

  • Runnable接口中的run()方法的返回值是void
  • Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

四、线程有哪些状态

  • 创建状态。
  • 就绪状态。
  • 运行状态。
  • 阻塞状态。
  • 死亡状态。

五、sleep() 和 wait() 有什么区别

  1. sleep()方法是Thread类的静态方法,是线程用来控制自身流程的。而wait()方法是Object类的方法,用于线程间的通信。
  1. 调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放锁。
  1. ① sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
    ② 调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

六、创建线程池有哪几种方式

  • ①. newFixedThreadPool(int nThreads)
    创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
  • ②. newCachedThreadPool()
    创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
  • ③. newSingleThreadExecutor()
    这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
  • ④. newScheduledThreadPool(intcorePoolSize)
    创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

七、怎么保证多线程的运行安全【并发编程三要素】

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
  • 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

八、多线程锁的升级原理是什么

  • 在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
  1. 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。
  2. 偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;如果线程处于活动状态,升级为轻量级锁的状态。
  3. 轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程B所访问,此时偏向锁就会升级为轻量级锁,线程B会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
  4. 重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

九、死锁是什么?怎么防止死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁的四个必要条件:
  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求和保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
  • 环路等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • 避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

十、ThreadLocal是什么?有哪些使用场景

  • 线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。

Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。

从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

十一、说一下 synchronized底层实现原理

  • synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
  • Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象

十二、synchronized和volatile的区别是什么

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

十三、synchronized 和 Lock 有什么区别

  • ① synchronized是java内置关键字,在jvm层面,Lock是个java接口;
  • ② synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • ③ synchronized会自动释放锁,Lock需在finally中手工释放锁(unlock()方法释放锁)
  • ④ synchronized线程A占用锁后,线程B会一直等待;而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
  • ⑤ synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
  • ⑥ Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

十四、synchronized和ReentrantLock区别是什么

  1. synchronized是关键字,ReentrantLock是类,这是二者的本质区别。
  2. ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量
ReentrantLock扩展性体现在几点上:
  • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
  • ReentrantLock可以获取各种锁的信息
  • ReentrantLock可以灵活地实现多路通知
  1. 二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。

十五、synchronized和Lock底层实现

  • synchronized用的锁是存在java对象里的,通过对代码反编译,可以看出被synchronized修饰的代码块,在执行之前先使用monitorenter指令加锁,然后在执行结束之后再使用monitorexit指令释放锁资源,在整个执行期间此代码都是锁定的状态,这就是典型悲观锁的实现流程。
  • lock锁使用的是CAS和volatile来实现同步的,CAS使用硬件命令实现缓存一致性保证了原子性,volatile保证了可见性,多线程环境下所有的线程通过CAS进行竞争资源,只能有一个成功,其它的都会自旋。

十六、在多线程中,什么是上下文切换

  • 上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。是多任务操作系统和多线程环境的基本特征。
原文地址:https://www.cnblogs.com/liqbk/p/13738198.html