大厂P7 Java程序员高频面试题-3

Posted 四猿外

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大厂P7 Java程序员高频面试题-3相关的知识,希望对你有一定的参考价值。

什么是并发容器的实现?

何为同步容器:可以简单地理解为通过synchronized 来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通过查看Vector, Hashtable 等这些同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchronized。

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

多线程同步和互斥有几种实现方法,都是什么?

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息, 当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的进程系统资源, 在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时, 任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

线程间的同步方法大体可分为两类: 用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。

用户模式下的方法有:原子操作(例如一个单一的全局变量), 临界区。内核模式下的方法有:事件,信号量,互斥量。

什么是竞争条件?你怎样发现和解决竞争?

当多个进程都企图对共享数据进行某种处理, 而最后的结果又取决于进程运行的顺序时, 则我们认为这发生了竞争条件( race condition)。

你将如何使用thread dump?你将如何分析Thread dump?

新建状态(New)
用new 语句创建的线程处于新建状态,此时它和其他Java 对象一样,仅仅在堆区中被分配了内存。

就绪状态(Runnable)
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java 虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中, 等待获得CPU 的使用权。

运行状态(Running)
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。

阻塞状态(Blocked)
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java 虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。

阻塞状态可分为以下3 种:

位于对象等待池中的阻塞状态( Blocked in object’s wait pool):

当线程处于运行状态时,如果执行了某个对象的wait()方法, Java 虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信” 的内容。

位于对象锁池中的阻塞状态(Blocked in object’ s lock pool) :

当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“ 线程同步”的内容。

其他阻塞状态(Otherwise Blocked):

当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时, 就会进入这个状态。

死亡状态(Dead)
当线程退出run()方法时, 就进入死亡状态, 该线程结束生命周期。

为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

当你调用start()方法时你将创建新的线程, 并且执行在run()方法里的代码。

但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码,只会把run 方法当作普通方法去执行。

Java 中你怎样唤醒一个阻塞的线程?

在Java 发展史上曾经使用suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。

解决方案可以使用以对象为目标的阻塞, 即利用Object 类的wait()和notify()方法实现线程阻塞。

首先,wait、notify 方法是针对对象的,调用任意对象的wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁, 直到获取成功才能往下执行;其次,wait、notify 方法必须在synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用wait、notify 方法的对象是同一个, 如此一来在调用wait 之前当前线程就已经成功获取某对象的锁,执行wait 阻塞后当前线程就将之前获取的对象锁释放。

在Java 中CycliBarriar 和CountdownLatch 有什么区别?

CyclicBarrier 可以重复使用,而CountdownLatch 不能重复使用。

Java 的concurrent 包里面的CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。

你可以向CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0 为止。

所以在当前计数到达零之前,await 方法会一直受阻塞。之后, 会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用CyclicBarrier。

CountDownLatch 的一个非常典型的应用场景是: 有一个任务想要往下执行, 但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch 对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch 对象上的countDown()方法,
这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch 对象的计数值减到0 为止。

CyclicBarrier 一个同步辅助类, 它允许一组线程互相等待, 直到到达某个公共屏障点(common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrier 很有用。因为该barrier 在释放等待线程后可以重用,所以称它为循环的barrier。

以上是关于大厂P7 Java程序员高频面试题-3的主要内容,如果未能解决你的问题,请参考以下文章

大厂P7 Java程序员高频面试题-6

大厂P7 Java程序员高频面试题-9

大厂P7 Java程序员高频面试题-2

大厂P7 Java程序员高频面试题-10

大厂P7 Java程序员高频面试题-12

大厂P7 Java程序员高频面试题-4