史上最全Java面试题!进程,线程相关部分下篇(带一律答案)

作者 : 开心源码 本文共5228个字,预计阅读时间需要14分钟 发布时间: 2022-05-12 共162人阅读

synchronized和ReentrantLock的区别

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

ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁?

ReentrantLock可以获取各种锁的信息?

ReentrantLock可以灵活地实现多路通知? 另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。

FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断能否已经完成、取消任务等操作。当然,因为FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

一个线程假如出现了运行时异常怎样办?

假如这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:假如这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。

Java当中有哪几种锁

自旋锁: 自旋锁在JDK1.6之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点白费,所以这里就做了一个解决,让后面请求锁的那个线程在稍等一会,但是不放弃解决器的执行时间,看看持有锁的线程是否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定。

偏向锁: 在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏,意思是这个锁会偏向第一个取得他的线程,假如接下来的执行过程中,改锁没有被其余线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不肯定总是对程序运行有利,假如程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑能否使用偏向锁。

轻量级锁: 为了减少取得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐步更新。锁可以更新但不能降级,意味着偏向锁更新成轻量级锁后不能降级成偏向锁。

如何在两个线程间共享数据

通过在线程之间共享对象即可以了,而后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比如说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。

如何正确的使用wait()?使用if还是while?

wait() 方法应该在循环调用,由于当线程获取到 CPU 开始执行的时候,其余条件可能还没有满足,所以在解决前,循环检测条件能否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

synchronized (obj) {

while (condition does not hold)

obj.wait(); // (Releases lock, and reacquires on wakeup)

? ? … // Perform action appropriate to condition

}

什么是线程局部变量ThreadLocal

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

ThreadLoal的作用是什么?

简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

生产者消费者模型的作用是什么?

通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用。

解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联络少,联络越少越可以独自发展而不需要收到相互的制约。

写一个生产者-消费者队列

可以通过阻塞队列实现,也可以通过wait-notify来实现。

使用阻塞队列来实现

//消费者

public class Producer implements Runnable{

private final BlockingQueue<Integer> queue;

public Producer(BlockingQueue q){

this.queue=q;

? }

? @Override

public void run() {

try {

while (true){

? ? ? ? ? ? ? Thread.sleep(1000);//模拟耗时

queue.put(produce());

? ? ? ? ? }

? ? ? }catch (InterruptedException e){

? ? ? }

? }

private int produce() {

int n=new Random().nextInt(10000);

? ? ? System.out.println(“Thread:” + Thread.currentThread().getId() + ” produce:” + n);

return n;

? }

}

//消费者

public class Consumer implements Runnable {

private final BlockingQueue<Integer> queue;

public Consumer(BlockingQueue q){

this.queue=q;

? }

? @Override

public void run() {

while (true){

try {

? ? ? ? ? ? ? Thread.sleep(2000);//模拟耗时

? ? ? ? ? ? ? consume(queue.take());

? ? ? ? ? }catch (InterruptedException e){

? ? ? ? ? }

? ? ? }

? }

private void consume(Integer n) {

? ? ? System.out.println(“Thread:” + Thread.currentThread().getId() + ” consume:” + n);

? }

}

//测试

public class Main {

public static void main(String[] args) {

? ? ? BlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(100);

? ? ? Producer p=new Producer(queue);

? ? ? Consumer c1=new Consumer(queue);

? ? ? Consumer c2=new Consumer(queue);

new Thread(p).start();

new Thread(c1).start();

new Thread(c2).start();

? }

}

使用wait-notify来实现

该种方式应该最经典,这里就不做说明了。

假如你提交任务时,线程池队列已满,这时会发生什么

假如你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续增加任务到阻塞队列中等待执行,由于LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;假如你使用的是有界队列比如说ArrayBlockingQueue的话,任务首先会被增加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler解决满了的任务,默认是AbortPolicy。

为什么要使用线程池

避免频繁地创立和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。

java中用到的线程调度算法是什么

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0)的作用是什么

因为Java采用抢占式的线程调度算法,因而可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

什么是CAS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS肯定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只需某次CAS操作失败,永远都不可能成功。

什么是乐观锁和悲观锁

乐观锁:乐观锁认为竞争不总是会发生,因而它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,假如失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:悲观锁认为竞争总是会发生,因而每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不论三七二十一,直接上了锁就操作资源了。

ConcurrentHashMap的并发度是什么?

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

ConcurrentHashMap的工作原理

ConcurrentHashMap在jdk 1.6和jdk 1.8实现原理是不同的。

jdk 1.6

ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其余线程必需阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其余线程对hash表其余地方的访问。? 具体实现:ConcurrentHashMap内部有一个Segment.

jdk 1.8

在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。

CyclicBarrier和CountDownLatch区别

这两个类非常相似,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

CyclicBarrier的某个线程运行到某个点上之后,该线程即中止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行。

CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务

CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。

java中的++操作符线程安全么?

不是线程安全的操作。它涉及到多个指令,如读取变量值,添加,而后存储回内存,这个过程可能会出现多个线程交差。

你有哪些多线程开发良好的实践?

给线程命名

最小化同步范围

优先使用volatile

尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore

优先使用并发容器而非同步容器.

考虑使用线程池

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 史上最全Java面试题!进程,线程相关部分下篇(带一律答案)

发表回复