多线程面试题整理(27题含答案)

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

  2. 了解守护线程吗?它和非守护线程有什么区别
    程序运行完毕,JVM会等待非守护线程完成后关闭,但是JVM不会等待守护线程,守护线程最经典的例子就是GC线程。

  3. 什么是多线程上下文切换
    多线程的上下文切换是指CPU控制权由一个已经正在进行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

  4. Runnable和Thread创建线程有什么区别?
    实现Runnable接口可能更优。
    原因有二:
    (1)Java不支持多继承,因此扩展Thread类就代表这个子类不能扩展其他类,而实现Runnable接口的类还可能扩展另一个类
    (2)类可能只要求可执行即可,因此继承整个Thread类的开销过大。

  5. Runnable和Callable的区别
    Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
    Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
    这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以方便获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。

  6. 产生死锁的条件
    (1)互斥条件:一个资源每次只能被一个进程使用
    (2)请求与保持条件:一个进程应请求资源而阻塞时,对已获得的资源保持不放。
    (3)不剥夺条件:进程已获得的资源,在未使用完之前,不得强行剥夺。
    (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

  7. 为什么wait()方法和notify()/notifyAll方法要在同步块中被调用
    这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁。

  8. 为什么wait,notify和notifyAll这些办法不放在Thread类当中
    一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。
    简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象。

  9. 怎么唤醒一个阻塞的线程
    如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以 中断线程,并且通过抛出InterruptedException来唤醒它;
    如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

  10. FutureTask是什么?
    这个其实前面有提到过,FutureTask表示一个异步运算的任务,FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取,判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

  11. 一个线程如果出现了运行时异常怎么办?
    如果这个异常没有被捕获的话,这个线程就停止执行了。
    另外重要的一点是:如果这个线程持有某个对象的监视器,那么这个对象监视器会被立刻释放。

  12. Java中有哪几种锁?
    (1)自旋锁:自旋锁在JDK1.6之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,所以这里就做了一个处理,让后面请求锁的那个线程再稍微等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。在jdk6之后,引入了自适应的自旋锁,也就是等待的事件不再固定了,而是由上一次在同一个锁上的自选时间及锁的拥有者状态来决定。
    (2)偏向锁:在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升线程的运行性能。偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中 大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。
    (3)轻量级锁:为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁喉不能降级成偏向锁。

  13. 如何在两个线程间共享数据
    通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。

  14. 如何正确的使用wait()?使用if还是while?
    wait()方法应该是在循环调用,因为当线程获取到CPU开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用wait和notify方法的代码:
    synchronized(obj){
    while(condition does not hold)
    obj.wait();
    //。。。。。。(这边略了)

  15. 什么是线程局部变量ThreadLocal?
    线程局部变量时局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理化境下(如web服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有是否,Java应用就存在内存泄露的风险。

  16. ThreadLocal的作用是什么?
    简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

  17. 生产者消费者模型的作用是什么?
    (1)提供平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者模型最重要的作用
    (2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约。

  18. 如果你提交任务时,线程池队列已满,这时会发生什么?
    如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;
    如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

  19. 为什么要使用线程池?
    避免频繁地创建和销毁线程,达到线程对象的重用。
    另外,使用线程池还可以根据项目灵活地控制并发的数目。

  20. Java中用到的线程调度算法是什么?
    抢占式。
    一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

  21. Thread.sleep(0)的作用是什么
    由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

  22. 什么是CAS
    CAS,全称为Compare and Swap,即比较-替换。
    假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某此CAS操作失败,永远都不可能成功。

  23. 什么是乐观锁和悲观锁
    乐观锁:乐观锁认为竞争不总是会发生,因此他不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

  1. ConcurrentHashMap的并发度是什么?
    ConcurrentHashMap的并发度就是segment的大小,默认是16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

  2. ConcurrentHashMap的工作原理
    ConcurrentHashMap在JDK1.6和JDK1.8实现原理是不同的。
    JDK1.6:ConcurrentHashMap是线程安全的,但是与Hashtable相比,实现线程安全的方式不同。
    Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。
    ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。
    具体实现:ConcurrentHashMap内部有一个Segment。
    JDK1.8中:ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是数组+链表->红黑树的实现。

  3. Java中的++操作符线程安全吗?
    不是线程安全的操作。他涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交叉。

  4. 有哪些多线程开发良好的实践?
    (1)给线程命名
    (2)最小化同步范围
    (3)优先使用volatile
    (4)尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore
    (5)优先使用并发容器而非同步容器
    (6)考虑使用线程池

https://www.kancloud.cn/smartsean/android/1106143
Android面试文档

猜你喜欢

转载自blog.csdn.net/ambitionLlll/article/details/115219364