关于面试:面试多线程

3次阅读

共计 6738 个字符,预计需要花费 17 分钟才能阅读完成。

多线程

35. 并行和并发有什么区别?

  • 并行是指多个事件在同一时刻产生;而并发是指多个事件在同一时间距离产生。
  • 并发:指利用可能交替执行不同的工作,其实并发有点相似于多线程的原理,多线程并非是同时执行多个工作而是疾速切换着执行。
  • 并行:指利用可能同时执行不同的工作, 例如,边吃饭边听音乐
  • 并发是指一个处理器同时解决多个工作。并行是指多个处理器或者是多核的处理器同时解决多个不同的工作。并发是逻辑上的同时产生, 而并行是物理上的同时产生。如 hadoop 分布式集群。

所以并发编程的指标是充沛的利用处理器的每一个核,以达到最高的解决性能。

36. 过程和线程的区别?

简而言之,过程是程序运行和资源分配的根本单位,一个程序至多有一个过程,一个过程至多有一个或多个线程。过程在执行过程中领有独立的内存单元,而多个线程共享内存资源 ,缩小切换次数,从而效率更高。** 线程是过程的一个实体
同一过程中的多个线程之间能够并发执行 ,是 cpu 调度和分派的根本单位,是比程序更小的能独立运行的根本单位。 线程是过程的一个实体
同一过程中的多个线程之间能够并发执行 **。

37. 守护线程是什么?

守护线程(即 daemon thread),是个服务线程,精确地来说就是服务其余的线程,这是它的作用——而其余的线程只有一种,那就是用户线程。所以 java 里线程分 2 种,
1、守护线程,比方垃圾回收线程,就是最典型的守护线程。
2、用户线程,就是应用程序里的自定义线程。
当所有非守护线程完结时,没有了被守护者,守护线程也就没有工作可做了,也就没有持续运行程序的必要了,程序也就终止了,同时会“杀死”所有守护线程。也就是说,只有有任何非守护线程还在运行,程序就不会终止。

38. 创立线程有哪几种形式?

①. 继承 Thread 类创立线程类

  • 继承 Thread 类,并重写该类的 run 办法,该 run 办法的办法体就代表了线程要实现的工作。因而把 run()办法称为执行体。
  • 创立 Thread 子类的实例,即创立了线程对象。
  • 调用线程对象的 start()办法来启动该线程。
public class FirstThreadTest extends Thread { 
    int i = 0; 
    // 重写 run 办法,run 办法的办法体就是线程执行体
    public void run() {for (; i < 100; i++) {System.out.println(getName() + " " + i);
        }
    } 
    public static void main(String[] args) {for (int i = 0; i < 100; i++) {//Thread.currentThread()办法返回以后正在执行的线程对象。//GetName()办法返回调用该办法的线程的名字。System.out.println(Thread.currentThread().getName() + ":" + i); 
             if (i == 50) { 
                // 创立 Thread 子类的实例,即创立了线程对象
                new FirstThreadTest().start(); 
                new FirstThreadTest().start();
            }
        }
    }

②. 通过 Runnable 接口创立线程类

  • 定义 runnable 接口的实现类,并重写该接口的 run()办法,该 run()办法的办法体同样是该线程的线程执行体。
  • 创立 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创立 Thread 对象,该 Thread 对象才是真正的线程对象。
  • 调用线程对象的 start()办法来启动该线程。

③. 通过 Callable 和 Future 创立线程

  • 创立 Callable 接口的实现类,并实现 call()办法,该 call()办法将作为线程执行体,并且有返回值。
  • 创立 Callable 实现类的实例,应用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()办法的返回值。
  • 应用 FutureTask 对象作为 Thread 对象的 target 创立并启动新线程。
  • 调用 FutureTask 对象的 get()办法来取得子线程执行完结后的返回值。

39. 说一下 runnable 和 callable 有什么区别?

有点深的问题了,也看出一个 Java 程序员学习常识的广度。

  • Runnable 接口中的 run()办法的返回值是 void,它做的事件只是纯正地去执行 run()办法中的代码而已;
  • Callable 接口中的 call()办法是有返回值的,是一个泛型,和 Future、FutureTask 配合能够用来获取异步执行的后果。

40. 线程有哪些状态?

线程通常都有五种状态,创立、就绪、运行、阻塞和死亡。

  • 创立状态。在生成线程对象,并没有调用该对象的 start 办法,这是线程处于创立状态。
  • 就绪状态。当调用了线程对象的 start 办法之后,该线程就进入了就绪状态,然而此时线程调度程序还没有把该线程设置为以后线程,此时处于就绪状态。在线程运行之后,从期待或者睡眠中回来之后,也会处于就绪状态。
  • 运行状态。线程调度程序将处于就绪状态的线程设置为以后线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。
  • 阻塞状态。线程正在运行的时候,被暂停,通常是为了期待某个工夫的产生 (比如说某项资源就绪) 之后再持续运行。sleep,suspend,wait 等办法都能够导致线程阻塞。
  • 死亡状态。如果一个线程的 run 办法执行完结或者调用 stop 办法后,该线程就会死亡。对于曾经死亡的线程,无奈再应用 start 办法令其进入就绪

41. sleep() 和 wait() 有什么区别?

sleep():办法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其余线程,等到休眠工夫完结后,线程进入就绪状态和其余线程一起竞争 cpu 的执行工夫。因为 sleep() 是 static 动态的办法,他不能扭转对象的机锁,当一个 synchronized 块中调用了 sleep() 办法,线程尽管进入休眠,然而对象的机锁没有被开释,其余线程仍然无法访问这个对象。

wait():wait()是 Object 类的办法,当一个线程执行到 wait 办法时,它就进入到一个和该对象相干的期待池,同时开释对象的机锁,使得其余线程可能拜访,能够通过 notify,notifyAll 办法来唤醒期待的线程

42. notify()和 notifyAll()有什么区别?

  • 如果线程调用了对象的 wait()办法,那么线程便会处于该对象的期待池中,期待池中的线程不会去竞争该对象的锁。
  • 当有线程调用了对象的 notifyAll()办法(唤醒所有 wait 线程)或 notify()办法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了 notify 后只有一个线程会由期待池进入锁池,而 notifyAll 会将该对象期待池内的所有线程挪动到锁池中,期待锁竞争。
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()办法,它才会从新回到期待池中。而竞争到对象锁的线程则持续往下执行,直到执行完了 synchronized 代码块,它会开释掉该对象锁,这时锁池中的线程会持续竞争该对象锁。

43. 线程的 run()和 start()有什么区别?

每个线程都是通过某个特定 Thread 对象所对应的办法 run()来实现其操作的,办法 run()称为线程体。通过调用 Thread 类的 start()办法来启动一个线程。

start()办法来启动一个线程,真正实现了多线程运行。这时无需期待 run 办法体代码执行结束,能够间接继续执行上面的代码;这时此线程是处于就绪状态,并没有运行。而后通过此 Thread 类调用办法 run()来实现其运行状态,这里办法 run()称为线程体,它蕴含了要执行的这个线程的内容,Run 办法运行完结,此线程终止。而后 CPU 再调度其它线程。

run()办法是在本线程里的,只是线程里的一个函数, 而不是多线程的。如果间接调用 run(), 其实就相当于是调用了一个一般函数而已,间接待用 run()办法必须期待 run()办法执行结束能力执行上面的代码,所以执行门路还是只有一条,基本就没有线程的特色,所以在多线程执行时要应用 start()办法而不是 run()办法。

44. 创立线程池有哪几种形式?

①. newFixedThreadPool(int nThreads)

创立一个固定长度的线程池,每当提交一个工作就创立一个线程,直到达到线程池的最大数量,这时线程规模将不再变动,当线程产生未预期的谬误而完结时,线程池会补充一个新的线程。

②. newCachedThreadPool()

创立一个可缓存的线程池,如果线程池的规模超过了解决需要,将主动回收闲暇线程,而当需要减少时,则能够主动增加新线程,线程池的规模不存在任何限度。

③. newSingleThreadExecutor()

这是一个单线程的 Executor,它创立单个工作线程来执行工作,如果这个线程异样完结,会创立一个新的来代替它;它的特点是能确保按照工作在队列中的程序来串行执行。

④. newScheduledThreadPool(int corePoolSize)

创立了一个固定长度的线程池,而且以提早或定时的形式来执行工作,相似于 Timer。

45. 线程池都有哪些状态?

线程池有 5 种状态:Running、ShutDown、Stop、Tidying、Terminated。

线程池各个状态切换框架图:

46. 线程池中 submit()和 execute()办法有什么区别?

  • 接管的参数不一样
  • submit 有返回值,而 execute 没有
  • submit 不便 Exception 解决

47. 在 java 程序中怎么保障多线程的运行平安?

线程平安在三个方面体现:

  • 原子性:提供互斥拜访,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
  • 可见性:一个线程对主内存的批改能够及时地被其余线程看到,(synchronized,volatile);
  • 有序性:一个线程察看其余线程中的指令执行程序,因为指令重排序,该察看后果个别杂乱无序,(happens-before 准则)。

48. 多线程锁的降级原理是什么?

在 Java 中,锁共有 4 种状态,级别从低到高顺次为:无状态锁,偏差锁,轻量级锁和重量级锁状态,这几个状态会随着竞争状况逐步降级。锁能够降级但不能降级。

锁降级的图示过程:

49. 什么是死锁?

死锁是指两个或两个以上的过程在执行过程中,因为竞争资源或者因为彼此通信而造成的一种阻塞的景象,若无外力作用,它们都将无奈推动上来。此时称零碎处于死锁状态或零碎产生了死锁,这些永远在相互期待的过程称为死锁过程。是操作系统层面的一个谬误,是过程死锁的简称,最早在 1965 年由 Dijkstra 在钻研银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计畛域最难解决的问题之一。

50. 怎么避免死锁?

死锁的四个必要条件:

  • 互斥条件:过程对所调配到的资源不容许其余过程进行拜访,若其余过程拜访该资源,只能期待,直至占有该资源的过程应用实现后开释该资源
  • 申请和放弃条件:过程取得肯定的资源之后,又对其余资源发出请求,然而该资源可能被其余过程占有,此事申请阻塞,但又对本人取得的资源放弃不放
  • 不可剥夺条件:是指过程已取得的资源,在未实现应用之前,不可被剥夺,只能在应用完后本人开释
  • 环路期待条件:是指过程产生死锁后,若干过程之间造成一种头尾相接的循环期待资源关系

这四个条件是死锁的必要条件,只有零碎产生死锁,这些条件必然成立,而只有上述条件之 一不满足,就不会产生死锁。

了解了死锁的起因,尤其是产生死锁的四个必要条件,就能够最大可能地防止、预防和 解除死锁。

所以,在零碎设计、过程调度等方面留神如何不让这四个必要条件成立,如何确 定资源的正当调配算法,防止过程永恒占据系统资源。

此外,也要避免过程在处于期待状态的状况下占用资源。因而,对资源的调配要给予正当的布局。

51. ThreadLocal 是什么?有哪些应用场景?

线程局部变量是局限于线程外部的变量,属于线程本身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来反对线程局部变量,是一种实现线程平安的形式。然而在治理环境下(如 web 服务器)应用线程局部变量的时候要特地小心,在这种状况下,工作线程的生命周期比任何利用变量的生命周期都要长。任何线程局部变量一旦在工作实现后没有开释,Java 利用就存在内存泄露的危险。

52. 说一下 synchronized 底层实现原理?

synchronized 能够保障办法或者代码块在运行时,同一时刻只有一个办法能够进入到临界区,同时它还能够保障共享变量的内存可见性。

Java 中每一个对象都能够作为锁,这是 synchronized 实现同步的根底:

  • 一般同步办法,锁是以后实例对象
  • 动态同步办法,锁是以后类的 class 对象
  • 同步办法块,锁是括号外面的对象

53. synchronized 和 volatile 的区别是什么?

  • volatile 实质是在通知 jvm 以后变量在寄存器(工作内存)中的值是不确定的,须要从主存中读取;synchronized 则是锁定以后变量,只有以后线程能够拜访该变量,其余线程被阻塞住。
  • volatile 仅能应用在变量级别;synchronized 则能够应用在变量、办法、和类级别的。
  • volatile 仅能实现变量的批改可见性,不能保障原子性;而 synchronized 则能够保障变量的批改可见性和原子性。
  • volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
  • volatile 标记的变量不会被编译器优化;synchronized 标记的变量能够被编译器优化。

54. synchronized 和 Lock 有什么区别?

  • 首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类;
  • synchronized 无奈判断是否获取锁的状态,Lock 能够判断是否获取到锁;
  • synchronized 会主动开释锁 (a 线程执行完同步代码会开释锁;b 线程执行过程中产生异样会开释锁),Lock 需在 finally 中手工开释锁(unlock() 办法开释锁),否则容易造成线程死锁;
  • 用 synchronized 关键字的两个线程 1 和线程 2,如果以后线程 1 取得锁,线程 2 线程期待。如果线程 1 阻塞,线程 2 则会始终期待上来,而 Lock 锁就不肯定会期待上来,如果尝试获取不到锁,线程能够不必始终期待就完结了;
  • synchronized 的锁可重入、不可中断、非偏心,而 Lock 锁可重入、可判断、可偏心(两者皆可);
  • Lock 锁适宜大量同步的代码的同步问题,synchronized 锁适宜代码大量的同步问题。

55. synchronized 和 ReentrantLock 区别是什么?

synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵便的个性,能够被继承、能够有办法、能够有各种各样的类变量,ReentrantLock 比 synchronized 的扩展性体现在几点上:

  • ReentrantLock 能够对获取锁的等待时间进行设置,这样就防止了死锁 
  • ReentrantLock 能够获取各种锁的信息
  • ReentrantLock 能够灵便地实现多路告诉 

另外,二者的锁机制其实也是不一样的:ReentrantLock 底层调用的是 Unsafe 的 park 办法加锁,synchronized 操作的应该是对象头中 mark word。

56. 说一下 atomic 的原理?

Atomic 包中的类根本的个性就是在多线程环境下,当有多个线程同时对单个(包含根本类型及援用类型)变量进行操作时,具备排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能胜利,而未胜利的线程能够向自旋锁一样,持续尝试,始终等到执行胜利。

Atomic 系列的类中的外围办法都会调用 unsafe 类中的几个本地办法。咱们须要先晓得一个货色就是 Unsafe 类,全名为:sun.misc.Unsafe,这个类蕴含了大量的对 C 代码的操作,包含很多间接内存调配以及原子操作的调用,而它之所以标记为非平安的,是通知你这个外面大量的办法调用都会存在安全隐患,须要小心应用,否则会导致重大的结果,例如在通过 unsafe 分配内存的时候,如果本人指定某些区域可能会导致一些相似 C ++ 一样的指针越界到其余过程的问题。

正文完
 0