下面是 Java 线程相关的热门面试题,你能够用它来好好准备面试。html
1) 什么是线程?java
线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运做单位。程序员能够经过它进行多处理器编程,你可使用多线程对运算密集型任务提速。好比,若是一个线程完成一个任务要 100 毫秒,那么用十个线程完成改任务只需 10 毫秒。Java 在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。欲了解更多详细信息请点击这里。程序员
2) 线程和进程有什么区别?面试
线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。别把它和栈内存搞混,每一个线程都拥有单独的栈内存用来存储本地数据。更多详细信息请点击这里。算法
3) 如何在 Java 中实现线程?数据库
在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程可是它须要调用 java.lang.Runnable 接口来执行,因为线程类自己就是调用的 Runnable 接口因此你能够继承 java.lang.Thread 类或者直接调用 Runnable 接口来重写 run ()方法实现线程。更多详细信息请点击这里.编程
4) 用 Runnable 仍是 Thread?设计模式
这个问题是上题的后续,你们都知道咱们能够经过继承 Thread 类或者调用 Runnable 接口来实现线程,问题是,那个方法更好呢?什么状况下使用它?这个问题很容易回答,若是你知道 Java 不支持类的多重继承,但容许你调用多个接口。因此若是你要继承其余类,固然是调用 Runnable 接口好了。更多详细信息请点击这里。缓存
6) Thread 类中的 start () 和 run () 方法有什么区别?安全
这个问题常常被问到,但仍是能今后区分出面试者对 Java 线程模型的理解程度。start ()方法被用来启动新建立的线程,并且 start ()内部调用了 run ()方法,这和直接调用 run ()方法的效果不同。当你调用 run ()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start ()方法才会启动新线程。更多讨论请点击这里
7) Java 中 Runnable 和 Callable 有什么不一样?
Runnable 和 Callable 都表明那些要在不一样的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增长的。它们的主要区别是 Callable 的 call () 方法能够返回值和抛出异常,而 Runnable 的 run ()方法没有这些功能。Callable 能够返回装载有计算结果的 Future 对象。个人博客有更详细的说明。
8) Java 中 CyclicBarrier 和 CountDownLatch 有什么不一样?
CyclicBarrier 和 CountDownLatch 均可以用来让一组线程等待其它线程。与 CyclicBarrier 不一样的是,CountdownLatch 不能从新使用。点此查看更多信息和示例代码。
9) Java 内存模型是什么?
Java 内存模型规定和指引 Java 程序在不一样的内存架构、CPU 和操做系统间有肯定性地行为。它在多线程的状况下尤为重要。Java 内存模型对一个线程所作的变更能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。好比,先行发生关系确保了:
我强烈建议你们阅读《Java 并发编程实践》第十六章来加深对 Java 内存模型的理解。
10) Java 中的 volatile 变量是什么?
volatile 是一个特殊的修饰符,只有成员变量才能使用它。在 Java 并发程序缺乏同步类的状况下,多线程对成员变量的操做对其它线程是透明的。volatile 变量能够保证下一个读取操做会在前一个写操做以后发生,就是上一题的 volatile 变量规则。点击这里查看更多 volatile 的相关内容。
11) 什么是线程安全?Vector 是一个线程安全类吗? (详见这里)
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的状况下也不会出现计算失误。很显然你能够将集合类分红两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它类似的 ArrayList 不是线程安全的。
12) Java 中什么是竞态条件? 举个例子说明。
竞态条件会致使程序在并发状况下出现一些 bugs。多线程对一些资源的竞争的时候就会产生竞态条件,若是首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不肯定的 bugs。这种 bugs 很难发现并且会重复出现,由于线程间的随机竞争。一个例子就是无序处理,详见答案。
13) Java 中如何中止一个线程?
Java 提供了很丰富的 API 但没有为中止线程提供 API。JDK 1.0 原本有一些像 stop (), suspend () 和 resume ()的控制方法可是因为潜在的死锁威胁所以在后续的 JDK 版本中他们被弃用了,以后 Java API 的设计者就没有提供一个兼容且线程安全的方法来中止一个线程。当 run () 或者 call () 方法执行完的时候线程会自动结束,若是要手动结束一个线程,你能够用 volatile 布尔变量来退出 run ()方法的循环或者是取消任务来中断线程。点击这里查看示例代码。
14) 一个线程运行时发生异常会怎样?
这是我在一次面试中遇到的一个很刁钻的 Java 面试题, 简单的说,若是异常没有被捕获该线程将会中止执行。Thread.UncaughtExceptionHandler 是用于处理未捕获异常形成线程忽然中断状况的一个内嵌接口。当一个未捕获异常将形成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler ()来查询线程的 UncaughtExceptionHandler 并将线程和异常做为参数传递给 handler 的 uncaughtException ()方法进行处理。
15) 如何在两个线程间共享数据?
你能够经过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java 线程间通讯》(涉及到在两个线程间共享对象)用 wait 和 notify 方法实现了生产者消费者模型。
16) Java 中 notify 和 notifyAll 有什么区别?
这又是一个刁钻的问题,由于多线程能够等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,可是这些方法没有彻底实现。notify ()方法不能唤醒某个具体的线程,因此只有一个线程在等待的时候它才有用武之地。而 notifyAll ()唤醒全部线程并容许他们争夺锁确保了至少有一个线程能继续运行。个人博客有更详细的资料和示例代码。
17) 为何 wait, notify 和 notifyAll 这些方法不在 thread 类里面?
这是个设计相关的问题,它考察的是面试者对现有系统和一些广泛存在但看起来不合理的事物的见解。回答这些问题的时候,你要说明为何把这些方法放在 Object 类里是有意义的,还有不把它放在 Thread 类里的缘由。一个很明显的缘由是 JAVA 提供的锁是对象级的而不是线程级的,每一个对象都有锁,经过线程得到。若是线程须要等待某些锁那么调用对象中的 wait ()方法就有意义了。若是 wait ()方法定义在 Thread 类中,线程正在等待的是哪一个锁就不明显了。简单的说,因为 wait,notify 和 notifyAll 都是锁级别的操做,因此把他们定义在 Object 类中由于锁属于对象。你也能够查看这篇文章了解更多。
18) 什么是 ThreadLocal 变量?
ThreadLocal 是 Java 里一种特殊的变量。每一个线程都有一个 ThreadLocal 就是每一个线程都拥有了本身独立的一个变量,竞争条件被完全消除了。它是为建立代价高昂的对象获取线程安全的好方法,好比你能够用 ThreadLocal 让 SimpleDateFormat 变成线程安全的,由于那个类建立代价高昂且每次调用都须要建立不一样的实例因此不值得在局部范围使用它,若是为每一个线程提供一个本身独有的变量拷贝,将大大提升效率。首先,经过复用减小了代价高昂的对象的建立个数。其次,你在没有使用高代价的同步或者不变性的状况下得到了线程安全。线程局部变量的另外一个不错的例子是 ThreadLocalRandom 类,它在多线程环境中减小了建立代价高昂的 Random 对象的个数。查看答案了解更多。
19) 什么是 FutureTask?
在 Java 并发程序中 FutureTask 表示一个能够取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,若是运算还没有完成 get 方法将会阻塞。一个 FutureTask 对象能够对调用了 Callable 和 Runnable 的对象进行包装,因为 FutureTask 也是调用了 Runnable 接口因此它能够提交给 Executor 来执行。
20) Java 中 interrupted 和 isInterruptedd 方法的区别?
interrupted () 和 isInterrupted ()的主要区别是前者会将中断状态清除然后者不会。Java 多线程的中断机制是用内部标识来实现的,调用Thread.interrupt ()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法Thread.interrupted ()来检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted ()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出 InterruptedException 异常的方法都会将中断状态清零。不管如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
21) 为何 wait 和 notify 方法要在同步块中调用?
主要是由于 Java API 强制要求这样作,若是你不这么作,你的代码会抛出 IllegalMonitorStateException 异常。还有一个缘由是为了不 wait 和 notify 之间产生竞态条件。
22) 为何你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出。所以,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在 notify ()方法调用以后和等待线程醒来以前这段时间它可能会改变。这就是在循环中使用 wait ()方法效果更好的缘由,你能够在 Eclipse 中建立模板调用 wait 和 notify 试一试。若是你想了解更多关于这个问题的内容,我推荐你阅读《Effective Java》这本书中的线程和同步章节。
23) Java 中的同步集合与并发集合有什么区别?
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 以前程序员们只有同步集合来用且在多线程并发的时候会致使争用,阻碍了系统的扩展性。Java5 介绍了并发集合像 ConcurrentHashMap,不只提供线程安全还用锁分离和内部分区等现代技术提升了可扩展性。更多内容详见答案。
24) Java 中堆和栈有什么不一样?
为何把这个问题归类在多线程和并发面试题里?由于栈是一块和线程紧密相关的内存区域。每一个线程都有本身的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是全部线程共享的一片公用内存区域。对象都在堆里建立,为了提高效率线程会从堆中弄一个缓存到本身的栈,若是多个线程使用该变量就可能引起问题,这时 volatile 变量就能够发挥做用了,它要求线程从主存中读取变量的值。
更多内容详见答案。
25) 什么是线程池? 为何要使用它?
建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。为了不这些问题,在程序启动的时候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你能够建立不一样的线程池。好比单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合不少生存期短的任务的程序的可扩展线程池)。更多内容详见这篇文章。
26) 如何写代码来解决生产者消费者问题?
在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通讯来解决这个问题。比较低级的办法是用 wait 和 notify 来解决这个问题,比较赞的办法是用 Semaphore 或者 BlockingQueue 来实现生产者消费者模型,这篇教程有实现它。
27) 如何避免死锁?
Java 多线程中的死锁
死锁是指两个或两个以上的进程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。这是一个严重的问题,由于死锁会让你的程序挂起没法完成任务,死锁的发生必须知足如下四个条件:
避免死锁最简单的方法就是阻止循环等待条件,将系统中全部的资源设置标志位、排序,规定全部的进程申请资源必须以必定的顺序(升序或降序)作操做来避免死锁。这篇教程有代码示例和避免死锁的讨论细节。
28) Java 中活锁和死锁有什么区别?
这是上题的扩展,活锁和死锁相似,不一样之处在于处于活锁的线程或进程的状态是不断改变的,活锁能够认为是一种特殊的饥饿。一个现实的活锁例子是两我的在狭小的走廊碰到,两我的都试着避让对方好让彼此经过,可是由于避让的方向都同样致使最后谁都不能经过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态能够改变可是却不能继续执行。
29) 怎么检测一个线程是否拥有锁?
我一直不知道咱们居然能够检测一个线程是否拥有锁,直到我参加了一次电话面试。在 java.lang.Thread 中有一个方法叫 holdsLock (),它返回 true 若是当且仅当当前线程拥有某个具体对象的锁。你能够查看这篇文章了解更多。
30) 你如何在 Java 中获取线程堆栈?
对于不一样的操做系统,有多种方法来得到 Java 进程的线程堆栈。当你获取线程堆栈时,JVM 会把全部线程的状态存到日志文件或者输出到控制台。在 Windows 你可使用 Ctrl + Break 组合键来获取线程堆栈,Linux 下用 kill -3 命令。你也能够用 jstack 这个工具来获取,它对线程 id 进行操做,你能够用 jps 这个工具找到 id。
31) JVM 中哪一个参数是用来控制线程的栈堆栈小的
这个问题很简单, -Xss 参数用来控制线程的堆栈大小。你能够查看 JVM 配置列表来了解这个参数的更多信息。
32) Java 中 synchronized 和 ReentrantLock 有什么不一样?
Java 在过去很长一段时间只能经过 synchronized 关键字来实现互斥,它有一些缺点。好比你不能扩展锁以外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 经过 Lock 接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具备可扩展性。你能够查看这篇文章了解更多
33) 有三个线程 T1,T2,T3,怎么确保它们按顺序执行?
在多线程中有多种方法让线程按特定顺序执行,你能够用线程类的 join ()方法在一个线程中启动另外一个线程,另一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3 调用 T2,T2 调用 T1),这样 T1 就会先完成而 T3 最后完成。你能够查看这篇文章了解更多。
34) Thread 类中的 yield 方法有什么做用?
Yield 方法能够暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法并且只保证当前线程放弃 CPU 占用而不能保证使其它线程必定能占用 CPU,执行 yield ()的线程有可能在进入到暂停状态后立刻又被执行。点击这里查看更多 yield 方法的相关内容。
35) Java 中 ConcurrentHashMap 的并发度是什么?
ConcurrentHashMap 把实际 map 划分红若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度得到的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程状况下就能避免争用。欲了解更多并发度和内部大小调整请阅读个人文章 How ConcurrentHashMap works in Java。
36) Java 中 Semaphore 是什么?
Java 中的 Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。若有必要,在许可可用前会阻塞每个 acquire (),而后再获取该许可。每一个 release ()添加一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采起相应的行动。信号量经常用于多线程的代码中,好比数据库链接池。更多详细信息请点击这里。
37)若是你提交任务时,线程池队列已满。会时发会生什么?
这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上若是一个任务不能被调度执行那么 ThreadPoolExecutor’s submit ()方法将会抛出一个 RejectedExecutionException 异常。
38) Java 线程池中 submit () 和 execute ()方法有什么区别?
两个方法均可以向线程池提交任务,execute ()方法的返回类型是 void,它定义在 Executor 接口中, 而 submit ()方法能够返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。更多详细信息请点击这里。
39) 什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不作其余事情,ServerSocket 的 accept ()方法就是一直等待客户端链接。这里的阻塞是指调用结果返回以前,当前线程会被挂起,直到获得结果以后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。更多详细信息请点击这里。
40) Swing 是线程安全的吗? 为何?
你能够很确定的给出回答,Swing 不是线程安全的,可是你应该解释这么回答的缘由即使面试官没有问你为何。当咱们说 swing 不是线程安全的经常提到它的组件,这些组件不能在多线程中进行修改,全部对 GUI 组件的更新都要在 AWT 线程中完成,而 Swing 提供了同步和异步两种回调方法来进行更新。点击这里查看更多 swing 和线程安全的相关内容。
41) Java 中 invokeAndWait 和 invokeLater 有什么区别?
这两个方法是 Swing API 提供给 Java 开发者用来从当前线程而不是事件派发线程更新 GUI 组件用的。InvokeAndWait ()同步更新 GUI 组件,好比一个进度条,一旦进度更新了,进度条也要作出相应改变。若是进度被多个线程跟踪,那么就调用 invokeAndWait ()方法请求事件派发线程对组件进行相应更新。而 invokeLater ()方法是异步调用更新组件的。更多详细信息请点击这里。
42) Swing API 中那些方法是线程安全的?
这个问题又提到了 swing 和线程安全,虽然组件不是线程安全的可是有一些方法是能够被多线程安全调用的,好比 repaint (), revalidate ()。 JTextComponent 的 setText ()方法和 JTextArea 的 insert () 和 append () 方法也是线程安全的。
43) 如何在 Java 中建立 Immutable 对象?
这个问题看起来和多线程没什么关系, 但不变性有助于简化已经很复杂的并发程序。Immutable 对象能够在没有同步的状况下共享,下降了对该对象进行并发访问时的同步化开销。但是 Java 没有@Immutable 这个注解符,要建立不可变类,要实现下面几个步骤:经过构造方法初始化全部成员、对变量不要提供 setter 方法、将全部的成员声明为私有的,这样就不容许直接访问这些成员、在 getter 方法中,不要直接返回对象自己,而是克隆对象,并返回对象的拷贝。个人文章 how to make an object Immutable in Java 有详细的教程,看完你能够充满自信。
44) Java 中的 ReadWriteLock 是什么?
通常而言,读写锁是用来提高并发程序性能的锁分离技术的成果。Java 中的 ReadWriteLock 是 Java 5 中新增的一个接口,一个 ReadWriteLock 维护一对关联的锁,一个用于只读操做一个用于写。在没有写线程的状况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可使用 JDK 中的 ReentrantReadWriteLock 来实现这个规则,它最多支持 65535 个写锁和 65535 个读锁。
45) 多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法 wait (), sleep () 或 yield () 它们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么作的目的是为了保留 CPU 缓存,在多核系统中,一个等待线程醒来的时候可能会在另外一个内核运行,这样会重建缓存。为了不重建缓存和减小等待重建的时间就可使用它了。你能够查看这篇文章得到更多信息。
46)volatile 变量和 atomic 变量有什么不一样?
这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不同。Volatile 变量能够确保先行关系,即写操做会发生在后续的读操做以前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操做就不是原子性的。而 AtomicInteger 类提供的 atomic 方法可让这种操做具备原子性如 getAndIncrement ()方法会原子性的进行增量操做把当前值加一,其它数据类型和引用变量也能够进行类似操做。
47) 若是同步块内的线程抛出异常会发生什么?
这个问题坑了不少 Java 程序员,若你能想到锁是否释放这条线索来回答还有点但愿答对。不管你的同步块是正常仍是异常退出的,里面的线程都会释放锁,因此对比锁接口我更喜欢同步块,由于它不用我花费精力去释放锁,该功能能够在 finally block 里释放锁实现。
48) 单例模式的双检锁是什么?
这个问题在 Java 面试中常常被问到,可是面试官对回答此问题的满意度仅为 50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5 是如何对它修正的。它实际上是一个用来建立线程安全的单例的老方法,当单例实例第一次被建立时它试图用单个锁进行性能优化,可是因为太过于复杂在 JDK1.4 中它是失败的,我我的也不喜欢它。不管如何,即使你也不喜欢它可是仍是要了解一下,由于它常常被问到。你能够查看 how double checked locking on Singleton works 这篇文章得到更多信息。
49) 如何在 Java 中建立线程安全的 Singleton?
这是上面那个问题的后续,若是你不喜欢双检锁而面试官问了建立 Singleton 类的替代方法,你能够利用 JVM 的类加载和静态变量初始化特征来建立 Singleton 实例,或者是利用枚举类型来建立 Singleton,我很喜欢用这种方法。你能够查看这篇文章得到更多信息。
50) 写出 3 条你遵循的多线程最佳实践
这种问题我最喜欢了,我相信你在写并发代码来提高性能的时候也会遵循某些最佳实践。如下三条最佳实践我以为大多数 Java 程序员都应该遵循:
51) 如何强制启动一个线程?
这个问题就像是如何强制进行 Java 垃圾回收,目前尚未以为方法,虽然你可使用 System.gc ()来进行垃圾回收,可是不保证能成功。在 Java 里面没有办法强制启动一个线程,它是被线程调度器控制着且 Java 没有公布相关的 API。
52) Java 中的 fork join 框架是什么?
fork join 框架是 JDK7 中出现的一款高效的工具,Java 开发人员能够经过它充分利用现代服务器上的多处理器。它是专门为了那些能够递归划分红许多子模块设计的,目的是将全部可用的处理能力用来提高程序的性能。fork join 框架一个巨大的优点是它使用了工做窃取算法,能够完成更多任务的工做线程能够从其它线程中窃取任务来执行。你能够查看这篇文章得到更多信息。
53) Java 多线程中调用 wait () 和 sleep ()方法有什么不一样?
Java 程序中 wait 和 sleep 都会形成某种形式的暂停,它们能够知足不一样的须要。wait ()方法用于线程间通讯,若是等待条件为真且其它线程被唤醒时它会释放锁,而 sleep ()方法仅仅释放 CPU 资源或者让当前线程中止执行一段时间,但不会释放锁。你能够查看这篇文章得到更多信息。
以上就是 50 道热门 Java 多线程和并发面试题啦。我没有分享全部题的答案但给将来的阅读者提供了足够的提示和线索来寻找答案。若是你真的找不到某题的答案,联系我吧,我会加上去的。这篇文章不只能够用来准备面试,还能检查你对多线程、并发、设计模式和竞态条件、死锁和线程安全等线程问题的理解。我打算把这篇文章的问题弄成全部 Java 多线程问题的大合集,可是没有你的帮助恐怖是不能完成的,你也能够跟我分享其它任何问题,包括那些你被问到却尚未找到答案的问题。这篇文章对初学者或者是经验丰富的 Java 开发人员都颇有用,过两三年甚至五六年你再读它也会受益不浅。它能够扩展初学者尤为有用由于这个能够扩展他们的知识面,我会不断更新这些题,你们能够在文章后面的评论中提问,分享和回答问题一块儿把这篇面试题完善。