多线程(面试题)

目录

1.Java 创建线程之后,直接调用 start()方法和 run()的区别?

2、线程 B 怎么知道线程 A 修改了变量?

3、synchronized 和 Volatile、CAS 比较?

4、线程间通信,wait 和 notify 的理解和使用?

5、 定时线程的使用?

6、 线程同步的方法?

7、进程和线程的区别?

8、什么叫线程安全?

9、线程的几种状态?

10、volatile 变量和 atomic 变量有什么不同?

11、Java 中什么是静态条件?

12、Java 中如何停止一个线程?

13、线程池的优点?

14、volatile 的理解?

15、 实现多线程有几种方式?

16、Java 中 notify 和 notifyAll 有什么区别?

17、什么是乐观锁和悲观锁?

18、线程的创建方式?

19、线程池的作用?

20、wait 和 sleep 的区别?

21、产生死锁的条件?

22、请写出实现线程安全的几种方式?

23、守护线程是什么?

24、什么是多线程的上下文切换?

25、Callable 和 Runnable 的区别是什么?

26、线程阻塞有哪些原因?

27、synchronized 和 Lock 的区别?

28、ThreadLocal 是什么?有什么作用?

29、交互方式分为同步和异步两种?

30、什么是线程?

31、什么是 FutureTask?

32、Java 中 interrupted 和 isInterruptedd 方法的区别?

33、死锁的原因?

34、什么是自旋

35、怎么唤醒一个阻塞的线程?

36、如果提交任务时,线程池队列已满,这时会发生什么?

37、什么是线程局部变量?

38、使用 volatile 关键字的场景?

39、线程池的工作原理,几个重要参数?         ThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactorythreadFactory,RejectedExecutionHandler handler)        

40、线程池的类型?

41、线程池的阻塞队列有哪些?

42、线程池的拒绝策略都有哪些?


1.Java 创建线程之后,直接调用 start()方法和 run()的区别?

        启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

2、线程 B 怎么知道线程 A 修改了变量?

        volatile 修饰变量synchronized 修饰修改变量的方法wait/notify while 轮询

3、synchronized 和 Volatile、CAS 比较?

        synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。volatile 提供多线程共享变量可见性和禁止指令重排序优化。CAS 是基于冲突检测的乐观锁(非阻塞)

4、线程间通信,wait 和 notify 的理解和使用?

        1 wait 和 notify 必须配合 synchronized 关键字使用。

        2 wait 方法释放锁,notify 方法不释放锁。

        3 还要注意一点 就是涉及到线程之间的通信,就肯定会用到 validate 修饰。

5、 定时线程的使用?

         1、普通线程死循环

        2、使用定时器 timer

        3、使用定时调度线程池 ScheduledExecutorService

6、 线程同步的方法?

        wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常。 notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

7、进程和线程的区别?

        1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。

        2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。

        3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

        4、系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。

8、什么叫线程安全?

        如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。

9、线程的几种状态?

        1、新建状态(New):新创建了一个线程对象。

        2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取 CPU 的使用权。即在就绪状态的进程除 CPU 之外,其它的运行所需资源都已全部获得。

        3、运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。

        4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。 阻塞的情况分三种: (1)、等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()或 notifyAll()方法才能被唤醒, (2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM会把该线程放入“锁池”中。 (3)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

        5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

10、volatile 变量和 atomic 变量有什么不同?

         volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count变量那么 count++ 操作就不是原子性的。而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如 getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

11、Java 中什么是静态条件?

        竞态条件会导致程序在并发情况下出现一些 bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了那么整个程序就会出现一些不确定的 bugs。这种 bugs 很难发现而且会重复出现,因为线程间的随机竞争。

12、Java 中如何停止一个线程?

        Java 提供了很丰富的 API 但没有为停止线程提供 API。JDK 1.0 本来有一些像 stop(), suspend()和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的 JDK 版本中他们被弃用了,之后 Java API 的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当 run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用 volatile 布尔变量来退出 run()方法的循环或者是取消任务来中断线程。

13、线程池的优点?

        1)重用存在的线程,减少对象创建销毁的开销。

        2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。

        3)提供定时执行、定期执行、单线程、并发数控制等功能。

14、volatile 的理解?

        volatile 关键字的两层语义 一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:

        1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

        2)禁止进行指令重排序。 用 volatile 修饰之后,变量的操作: 第一:使用 volatile 关键字会强制将修改的值立即写入主存;第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1 或者 L2 缓存中对应的缓存行无效); 第三:由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1 再次读取变量 stop的值时会去主存读取。

15、 实现多线程有几种方式?

        在 语 言 层 面 有 两 种 方 式 。 java.lang.Thread 类 的 实 例 就 是 一 个 线 程 但 是 它 需 要 调 用java.lang.Runnable 接口来执行,由于线程类本身就是调用的 Runnable 接口所以你可以继承java.lang.Thread 类或者直接调用 Runnable 接口来重写 run()方法实现线程。

16、Java 中 notify 和 notifyAll 有什么区别?

        notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

17、什么是乐观锁和悲观锁?

        1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

        2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

18、线程的创建方式?

        方式一:继承 Thread 类 方式二:实现 Runnable 接口方式三:实现 Callable 接口 方式四:使用线程池的方式

19、线程池的作用?

        创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。

20、wait 和 sleep 的区别?

        sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

21、产生死锁的条件?

        1、互斥条件:一个资源每次只能被一个进程使用。

        2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

        3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

        4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

22、请写出实现线程安全的几种方式?

        方式一:使用同步代码块方式二:使用同步方法 方式三:使用 ReentrantLock

23、守护线程是什么?

        它和非守护线程的区别? 程序运行完毕,jvm 会等待非守护线程完成后关闭,但是 jvm 不会等待守护线程.守护线程最典型的例子就是 GC 线程.

24、什么是多线程的上下文切换?

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

25、Callable 和 Runnable 的区别是什么?

        两者都能用来编写多线程,但实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable接口的任务线程不能返回结果.Callable通常需要和Future/FutureTask结合使用,用于获取异步计算结果.

26、线程阻塞有哪些原因?

        1、sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到 CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止

        2、suspend() 和 resume() 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。

        3、yield() 使当前线程放弃当前已经分得的 CPU 时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程

        4、wait() 和 notify() 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.

27、synchronized 和 Lock 的区别?

        主要相同点:Lock 能完成 synchronized 所实现的所有功能 主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。

28、ThreadLocal 是什么?有什么作用?

        ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。 简单说 ThreadLocal 就是一种以空间换时间的做法,在每个 Thread 里面维护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

29、交互方式分为同步和异步两种?

        同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;

        异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。

30、什么是线程?

        线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速

31、什么是 FutureTask?

        在 Java 并发程序中 FutureTask 表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包 装,由于 FutureTask 也是调用了 Runnable 接口所以它可以提交给 Executor 来执行。

32、Java 中 interrupted 和 isInterruptedd 方法的区别?

        interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java 多线程的中断机制是用内部标识来实现的,调用 Thread.interrupt()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出 InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

33、死锁的原因?

        1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。例如:线程在获得了锁 A 并且没有释放的情况下去申请锁 B,这时,另一个线程已经获得了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。

        2)默认的锁申请操作是阻塞的。 所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。

34、什么是自旋

        很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized 的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

35、怎么唤醒一个阻塞的线程?

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

36、如果提交任务时,线程池队列已满,这时会发生什么?

        许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么 ThreadPoolExecutor’s submit()方法将会抛出一个 RejectedExecutionException 异常。

37、什么是线程局部变量?

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

38、使用 volatile 关键字的场景?

        synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile必须具备以下 2 个条件: 1)对变量的写操作不依赖于当前值 2)该变量没有包含在具有其他变量的不变式中

39、线程池的工作原理,几个重要参数?         ThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactorythreadFactory,RejectedExecutionHandler handler)        

        参数说明:                

        corePoolSize 核心线程数

        maximumPoolSize 最大线程数,一般大于等于核心线程数

        keepAliveTime 线程存活时间(针对最大线程数大于核心线程数时,非核心线程)

        unit 存活时间单位,和线程存活时间配套使用

        workQueue 任务队列

        threadFactory 创建线程的工程 handler 拒绝策略

40、线程池的类型?

        五种线程池: ExecutorService threadPool = null;

        threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制

        threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池

        threadPool = Executors.newScheduledThreadPool(2);

        threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作

        threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多

41、线程池的阻塞队列有哪些?

        三种阻塞队列:

        BlockingQueue<Runnable> workQueue = null;

        workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界

        workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界

        workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界

42、线程池的拒绝策略都有哪些?

        四种拒绝策略 等待队列已经排满了,再也塞不下新任务,同时线程池中线程也已经达到 maximumPoolSize数量,无法继续为新任务服务,这个时候就需要使用拒绝策略来处理。RejectedExecutionHandler rejected = null;

        rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常,直接抛出 RejectedExecutionException 异常阻止系统正常运行。

        rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常,直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

        rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列, 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

        rejected = new ThreadPoolExecutor.CallerRunsPoliccy();//如果添加到线程池失败,那么主线程会自己去执行该任务,调用者运行”一种调节机制,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失忆机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值