每天十道面试题-20200402

题目

  • 1、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?
  • 2、什么是 Executors 框架?
  • 3、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?
  • 4、什么是 Callable 和 Future?
  • 5、什么是 FutureTask使用 ExecutorService 启动任务?
  • 6、什么是并发容器的实现?
  • 7、多线程同步和互斥有几种实现方法,都是什么?
  • 8、什么是竞争条件?你怎样发现和解决竞争?
  • 9、你将如何使用 thread dump?你将如何分析 Thread dump?
  • 10、为什么我们调用 start()方法时会执行 run()方法,为什么我们不能直接调用 run()方法?

解答

题目一
  • 题干:Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

  • 分析:

  • 考察内置锁synchronized和Lock锁的区别,首先synchronized是jdk的一种内置锁,它是一种悲观的、可重入的、不公平的、重量级的【需要调用操作系统的相关接口】锁,jdk1.6之后对synchronized进行了一些优化,如锁消除、自旋锁、适应性自旋、偏向锁、轻量级锁,而Lock相对于synchronized来讲是一种更高级的锁,因为synchronized是一种互斥锁,使用synchronized时我们无法中断一个获取锁的线程,而且在获取锁的时候如果获取不到则会一直等待下去,而且需要使用在代码块上,当然它也有自身的好处,作为关键字使用简单,不需要显示释放锁,引入Lock锁就是来解决synchronized的一些劣势的,提供了tryLock可以防止一直等待下去有超时参数,提供lockInterruptibly方法来中断获取锁的线程,而且加锁的时候没有规定必须要在代码块中使用,当然它也有不好的地方,需要显示的释放在finally中显示释放锁,如果不释放会出现问题,此外Lock的实现类ReentrantLock提供了公平锁和非公平锁的选择,但是数据证明,非公平锁性能更好,故虽然Lock锁更高级一些,如果synchronized能够满足我们的使用时就没必要使用Lock,如果Lock满足不了在使用。
    synchronized的源码:每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加 上monitorenter和monitorexit指令来实现的,方法加锁是通过一个标记位来判断的。

  • 回答:

  • 区别见分析部分源码-1、JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下, ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将 一部分线程移动到EntryList中作为候选竞争线程。 2. Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定 EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。 3. Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck, OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在 JVM中,也把这种选择行为称之为“竞争切换”。 4. OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList 中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify 或者notifyAll唤醒,会重新进去EntryList中。 5. 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统 来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。 6. Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先 尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是 不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁 资源。 参考:https://blog.csdn.net/zqz_zqz/article/details/70233767 7. 每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加 上monitorenter和monitorexit指令来实现的,方法加锁是通过一个标记位来判断的 8. synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线 程加锁消耗的时间比有用操作消耗的时间更多。

题目二
  • 题干:什么是 Executors 框架?
  • 分析:
  • Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。
    无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池。

  • 回答:
  • 见分析.

题目三
  • 题干:什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?
  • 分析:
  • 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
    这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
    阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
    JDK7提供了7个阻塞队列。分别是:
    ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
    LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
    PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
    DelayQueue:一个使用优先级队列实现的无界阻塞队列。
    SynchronousQueue:一个不存储元素的阻塞队列。
    LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
    LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
    Java 5之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实现生产者,消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized这些关键字。而在java 5之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。
    BlockingQueue接口是Queue的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工具,因此他具有一个很明显的特性,当生产者线程试图向BlockingQueue放入元素时,如果队列已满,则线程被阻塞,当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞,正是因为它所具有这个特性,所以在程序中多个线程交替向BlockingQueue中放入元素,取出元素,它可以很好的控制线程之间的通信。
    阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。。

  • 回答:
  • 见分析

题目四
  • 题干:什么是 Callable 和 Future?
  • 分析:
  • Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
    可以认为是带有回调的Runnable。
    Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

  • 回答:
  • Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
    可以认为是带有回调的Runnable。
    Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

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

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

题目六
  • 题干:什么是并发容器的实现?
  • 分析:
  • 并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以在并发环境下实现更高的吞吐量。

  • 回答:
  • 并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以在并发环境下实现更高的吞吐量。

题目七
  • 题干:多线程同步和互斥有几种实现方法,都是什么?
  • 分析:
  • 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
    线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
    线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
    用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

  • 回答:
  • 见分析

题目八
  • 题干:什么是竞争条件?你怎样发现和解决竞争?
  • 分析:
  • 当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件(race condition)。

  • 回答:
题目九
  • 题干:你将如何使用 thread dump?你将如何分析 Thread dump?
  • 分析:
  • 连接

  • 回答:
  • 见分析

题目十
  • 题干:为什么我们调用 start()方法时会执行 run()方法,为什么我们不能直接调用 run()方法?
  • 分析:
  • 当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。
    但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码,只会把run方法当作普通方法去执行。

  • 回答:
  • 当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。
    但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码,只会把run方法当作普通方法去执行。

发布了122 篇原创文章 · 获赞 32 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/YangzaiLeHeHe/article/details/105292993