线程相关面试题目汇总

线程相关面试题目汇总

  • 1、线程
    • (1)进程、线程、协程
      • 进程、线程、协程之间的区别?
      • 进程和线程之间的关系
      • 线程安全是什么意思?
      • 如何创建线程?
      • 什么是守护线程?
    • (2)线程的状态
      • 线程的五种状态分别是什么?
      • 线程会出现哪些情况?
    • (3)线程的常用方法
      • start()与run()的区别?
      • wait()和sleep()的区别?
      • 线程a,b,c,d运⾏任务,怎么保证当a,b,c线程执⾏完再执⾏d线程?(join方法)
      • stop() 和 suspend() 方法为何不推荐使用?
      • 线程同步用到的方法有哪些?
      • 线程 yield()方法有什么用?
  • 2、多线程
    • (1)线程池
      • 线程池的优点有哪些?
      • 什么是Executor框架?
      • 线程池有哪些参数?主要流程是怎样的?
    • (2)同步
      • 如何理解同步和异步?
      • 请谈谈volatile有什么特点,为什么它能保证变量对所有线程的可见性?
      • 线程同步的方法
  • 3、锁
    • (1)乐观/悲观锁
      • 什么是乐观锁、悲观锁?
      • 什么是CAS?
    • (2)Synchronized
      • synchronized的原理是什么?
      • synchronized和ReentrantLock的区别?
      • 谈一谈Synchronized和Lock的异同?
      • java中加锁的⽅式有哪些?

1、线程

(1)进程、线程、协程

进程、线程、协程之间的区别?

进程是系统进行资源分配和调度的一个独立单位,拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度。是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

协程协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。

进程和线程之间的关系

进程可以看作一个程序或者一个应用,是一个独立的运行环境。而线程是在进程中执行的一个任务,一个进程可以多个任务同时执行,即多线程。不同的进程使用不同的内存空间,但是所有线程共享一片相同的内存空间。线程是进程的子集,一个进程可以有很多线程,但是一个线程只能属于一个进程。

线程安全是什么意思?

多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏ 访问,直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。

如何创建线程?

1、实现Runnable接口 ,重写run⽅法,实现Runnable接⼝的实现类的实例对象作为Thread构造函数的target

2、继承Thread类,重写run⽅法

3、实现Callable接口通过FutureTask包装器来创建Thread线程

4、通过线程池创建

什么是守护线程?

与守护线程相对应的就是用户线程,守护线程就是守护用户线 程,当用户线程全部执行完结束之后,守护线程才会跟着结束。也就是守护线程必 须伴随着用户线程,如果一个应用内只存在一个守护线程,没有用户线程,守护线 程自然会退出。

(2)线程的状态

线程的五种状态分别是什么?

线程相关面试题目汇总_第1张图片

1、新建:对象创建时,没有调用该对象的start方法,此时处于线程的新建状态。

2、就绪:调用了start方法之后,该对象进入了就绪状态,但是当前线程调度程序还没有把该线程设置为当前线程,便处于就绪状态。在线程运行之后从等待或者睡眠状态中回来之后也会处于就绪状态。

3、运行:线程调度程序将处于就绪态的线程设置为当前线程,此时线程处于运行状态,开始运行run方法。

4、阻塞:线程在运行时被暂停(通常是为了等待某个事件的发生之后再继续运行)。sleep、wait、suspend等方法会导致线程阻塞。

5、死亡:一个线程run方法执行结束或者调用stop方法后,线程会死亡。对于死亡的线程,无法再使用start方法使其进入就绪。

线程会出现哪些情况?

活锁、饥饿、无锁、死锁。

死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现 了这三种情况,即线程不再活跃,不能再正常地执行下去了。

死锁:死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等 对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。

活锁:活锁这个概念大家应该很少有人听说或理解它的概念,而在多线程中这确实存在。 活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿 到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别 的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁。

饥饿:优先级高的线程能够插队并优先执 行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无 法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源 不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够 得到执行的,如那个占用资源的线程结束了并释放了资源

无锁:即没有对资源进行锁定,即所有的线程都能访问并修改同一个资源,但同时 只有一个线程能修改成功。无锁典型的特点就是一个修改操作在一个循环内进行, 线程会不断的尝试修改共享资源,如果没有冲突就修改成功并退出否则就会继续下 一次循环尝试。所以,如果有多个线程修改同一个值必定会有一个线程能修改成功, 而其他修改失败的线程会不断重试直到修改成功。(CAS)

(3)线程的常用方法

start()与run()的区别?

start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕.
run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码

wait()和sleep()的区别?

1、sleep() ⽅法是线程类(Thread)的静态⽅法,让调⽤线程进⼊睡眠状态,让出执⾏机会给其他线程,等到休眠时间结束后,线程进⼊就绪状态和其他线程⼀起竞争cpu的执⾏时间。 因为sleep() 是static静态的⽅法,他不能改变对象的机锁,当⼀个synchronized块中调⽤了sleep() ⽅法,线程虽然进⼊休眠,但是对象的机锁没有被释放,其他线程依然⽆法访问这个对象。

2、wait() wait()是Object类的⽅法,当⼀个线程执⾏到wait⽅法时,它就进⼊到⼀个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll⽅法来唤醒等待的线程

线程a,b,c,d运⾏任务,怎么保证当a,b,c线程执⾏完再执⾏d线程?(join方法)

1、使用join()方法,将线程B加⼊到线程A的尾部,当A执⾏完后B才执⾏

2、CountDownLatch类:⼀个同步辅助类,常⽤于某个条件发⽣后才能执⾏后续进程。给定计数初始化CountDownLatch,调⽤countDown()⽅ 法,在计数到达零之前,await⽅法⼀直受阻塞。

3、notify、wait⽅法,Java中的唤醒与等待⽅法,关键为synchronized代码块,参数线程间应相同,也常⽤Object作为参数。

stop() 和 suspend() 方法为何不推荐使用?

stop()是不安全的,会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。

suspend()方法容易造成死锁 调用 suspend() 的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被 “挂起” 的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。

线程同步用到的方法有哪些?

wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException 异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。

notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

线程 yield()方法有什么用?

Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。 它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定 能占用 CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

2、多线程

(1)线程池

线程池的优点有哪些?

1、减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务

2、可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为因为消耗过多的内存,⽽把服务器累趴下(线程开的越多,消耗的内存也越大,最后死机)

什么是Executor框架?

Executor提供四种线程池:

1、newCachedThreadPool创建⼀个可缓存线程池,如果线程池⻓度超过处理需要,可灵活回收空闲线程,若⽆可 回收,则新建线程。

2、newFixedThreadPool 创建⼀个定⻓线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。

3、newScheduledThreadPool 创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。

4、newSingleThreadExecutor 创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务 按照指定顺序(FIFO, LIFO, 优先级)执⾏。

线程池有哪些参数?主要流程是怎样的?

重要参数:

corePoolSize :线程池核心线程大小,线程池的最小线程数量。即使这些线程处于空闲状态也不会被销毁。

maximumPoolSize :线程池的最大线程数量。

keepAliveTime :空闲线程的存活时间,一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁。

unit :存活时间单位。

workQueue :工作队列,新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。

threadFactory :创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

handler :当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。

主要流程:

线程相关面试题目汇总_第2张图片

1 线程池创建时,里面没有一个线程,任务队列是作为参数传进来的。不过,就算队列⾥⾯有任务,线程池也不会⻢上执⾏它们。

2 当调用execute()添加一个任务时,会经历如下判断:

  • 如果正在运⾏的线程数量⼩于 corePoolSize,那么⻢上创建线程运⾏这个任务;

  • 如果正在运⾏的线程数量⼤于或等于 corePoolSize,那么将这个任务放⼊队列;

  • 如果这时候队列满了,⽽且正在运⾏的线程数量⼩于 maximumPoolSize,那么还是要创建⾮核⼼线程⽴刻运⾏这个 任务;

  • 如果队列满了,⽽且正在运⾏的线程数量⼤于或等于 maximumPoolSize,那么线程池会抛出异常 RejectExecutionException。

  • 当⼀个线程完成任务时,它会从队列中取下⼀个任务来执⾏。

  • 当⼀个线程⽆事可做,超过⼀定的时间(keepAliveTime)时,线程池会判断,如果当前运⾏的线程数⼤于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的⼤⼩。

(2)同步

如何理解同步和异步?

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

请谈谈volatile有什么特点,为什么它能保证变量对所有线程的可见性?

关键字volatile是Java虚拟机提供的最轻量级的同步机制。当一个变量被定义成volatile之后,具备两种特性:
1.保证此变量对所有线程的可见性。当一条线程修改了这个变量的值,新值对于其他线程是可以立即得知的。而普通变量做不到这一点。每次读取到volatile变量,一定是最新的数据。

2.禁止指令重排序优化。普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不保证程序代码的执行顺序。volatile的一个重要作用就是和CAS结合,保证了原子性

线程同步的方法

参考1(3)线程同步用到的方法有哪些?

3、锁

(1)乐观/悲观锁

什么是乐观锁、悲观锁?

乐观锁:乐观锁假设认为数据⼀般情况下不会造成冲突, 所以在数据进⾏提交更新的时候,才会正式对数据的冲突与否进⾏检测,如果发现冲突了,则让返回⽤户错误的信息(CAS)。

乐观锁缺点:

1 乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但观锁将变得力不从心

2 长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。

3 ABA问题。CAS的核心思ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。

悲观锁:这种通过使⽤⼀致的锁定协议来协调对共享状态的访问,可以确保⽆论哪个线程持有共享变量的锁,都采⽤独占的⽅式来访问这些变量。(不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。)

什么是CAS?

compare and swap

CAS是项乐观锁技术。CPU 去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。

CAS 操作包含三个操作数 —— 内存位置 (V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会⾃动将该位置值更新为新 值。否则,处理器不做任何操作。⽆论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我 认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在 的值即可。”

(2)Synchronized

synchronized的原理是什么?

Synchronized是由JVM实现的一种实现互斥同步的一种方式,如果你查看被Synchronized修饰过的 程序块编译后的字节码,会发现,被Synchronized 修饰过的程序块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令 。

在虚拟机执行到monitorenter指令时,首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+1;当执行monitorexit指令时将锁计数器-1;当计数器为0时,锁就被释放了。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。Java中Synchronize通过在对象头设置标记,达到了获取锁和释放锁的目的。

synchronized和ReentrantLock的区别?

从锁的分类考虑

  • 可重入锁。可重⼊锁是指同⼀个线程可以多次获取同⼀把锁。ReentrantLock和synchronized都是可重⼊锁。
  • 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,⽽ ReentrantLock则提供了中断功能。
  • 公平锁与⾮公平锁。公平锁是指多个线程同时尝试获取同⼀把锁时,获取锁的顺序按照线程达到的顺序,⽽⾮公平锁则允许线程“插队”。synchronized是⾮公平锁,⽽ReentrantLock的默认实现是⾮公平锁,但是也可以设置为公平锁。

Synchronized:synchronized是java内置的关键字,它提供了⼀种独占的加锁⽅式。synchronized的获取和释放锁由JVM实现,⽤户不需要显示的释放锁,⾮常⽅便。

ReentrantLock:ReentrantLock它是JDK 1.5之后提供的API层⾯的互斥锁,需要lock()和unlock()⽅法配合try/finally语句块来完成。

谈一谈Synchronized和Lock的异同?

共同点:Lock 能完成 synchronized 所实现的所有功能。

不同点:synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在finally从句中释放。Lock 还有更强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿 锁。

java中加锁的⽅式有哪些?

java中有两种锁:⼀种是⽅法锁或者对象锁(在⾮静态⽅法或者代码块上加锁),第⼆种是类锁(在静态⽅法或者class上加锁);

对象锁:“锁”的本质其实是monitorenter和monitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。我们知道,使用Synchronized可以修饰不同的对象,因此,对应的对象锁可以这么确定:

1.如果Synchronized明确指定了锁对象,比如Synchronized(变量名)、Synchronized(this)等,说明加解锁对象为该对象。
2.如果没有明确指定:若Synchronized修饰的方法为非静态方法,表示此方法对应的对象为锁对象;若Synchronized修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。注意,当一个对象被锁住时,对象里面所有用Synchronized修饰的方法都将产生堵塞,而对象里非Synchronized修饰的方法可正常被调用,不受锁影响。

你可能感兴趣的:(学习整理,不失业计划,个人学习,多线程,java)