面试卡在多线程?那就分享几道 Java 多线程高频面试题,面试不用愁

本文深入剖析了Java多线程中的关键概念,包括忙循环、自旋锁、互斥锁及其优缺点,强调自旋锁在短时等待和低竞争情况下的高效性。此外,探讨了线程间共享数据的策略,如使用Runnable和Callable接口,并解释了Java中CyclicBarrier和CountDownLatch的区别。最后,详细分析了ConcurrentHashMap的内部实现和高并发性能提升的原因,以及阻塞队列BlockingQueue的使用和线程池ThreadPoolExecutor的工作原理与配置策略。
摘要由CSDN通过智能技术生成
  1. 多线程中的忙循环是什么?忙循环就是程序员用循环让一个线程等待,不像传统方法 wait()、 sleep() 或 yield(),它们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。

这么做的目的是为了保留 CPU 缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

  1. 什么是自旋锁?没有获得锁的线程一直循环在那里看是否该锁的保持者已经释放了锁,这就是自旋锁。

  2. 什么是互斥锁?互斥锁:从等待到解锁过程,线程会从 sleep 状态变为 running 状态,过程中有线程上下文的切换,抢占 CPU 等开销。

  3. 自旋锁的优缺点?自旋锁不会引起调用者休眠,如果自旋锁已经被别的线程保持,调用者就一直循环在那里看是否该自旋锁的保持者释放了锁。由于自旋锁不会引起调用者休眠,所以自旋锁的效率远高于互斥锁。

虽然自旋锁效率比互斥锁高,但它会存在下面两个问题: 1、自旋锁一直占用 CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致 CPU 效率降低。 2、试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

  1. 如何在两个线程间共享数据?同一个 Runnable,使用全局变量。

第一种:将共享数据封装到一个对象中,把这个共享数据所在的对象传递给不同的 Runnable

第二种:将这些 Runnable 对象作为某一个类的内部类,共享的数据作为外部类的成员变量,对共享数据的操作分配给外部类的方法来完成,以此实现对操作共享数据的互斥和通信,作为内部类的 Runnable 来操作外部类的方法,实现对数据的操作

class ShareData {private int x = 0;

public synchronized void addx(){x++;System.out.println("x++ : "+x);}public synchronized void subx(){x--;System.out.println("x-- : "+x);}}

public class ThreadsVisitData {

public static ShareData share = new ShareData();

public static void main(String[] args) {//final ShareData share = new ShareData();new Thread(new Runnable() {public void run() {for(int i = 0;i<100;i++){share.addx();}}}).start();new Thread(new Runnable() {public void run() {for(int i = 0;i<100;i++){share.subx();}}}).start();}}

  1. Java 中 Runnable 和 Callable 有什么不同?Runnable 和 Callable 都是接口, 不同之处: 1.Callable 可以返回一个类型 V,而 Runnable 不可以 2.Callable 能够抛出 checked exception,而 Runnable 不可以。 3.Runnable 是自从 java1.1 就有了,而 Callable 是 1.5 之后才加上去的 4.Callable 和 Runnable 都可以应用于 executors。而 Thread 类只支持 Runnable.

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class ThreadTestB {

public static void main(String[] args) {

ExecutorService e=Executors.newFixedThreadPool(10);

Future f1=e.submit(new MyCallableA());

Future f2=e.submit(new MyCallableA());

Future f3=e.submit(new MyCallableA());

System.out.println("--Future.get()....");

try {

System.out.println(f1.get());

System.out.println(f2.get());

System.out.println(f3.get());

} catch (InterruptedException e1) {

e1.printStackTrace();

} catch (ExecutionException e1) {

e1.printStackTrace();

}

e.shutdown();

}

}

class MyCallableA implements Callable<String>{

public String call() throws Exception {

System.out.println("开始执行 Callable");

String[] ss={"zhangsan","lisi"};

long[] num=new long[2];

for(int i=0;i<1000000;i++){

num[(int)(Math.random()*2)]++;

}

    if(num[0]>num[1]){          return ss[0];      }else if(num[0]<num[1]){          throw new Exception("弃权!");      }else{          return ss[1];      }  } 

复制代码

}

7. Java 中 CyclicBarrier 和 CountDownLatch 有什么不同?CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待,只不过它们侧重点不同:

CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才执行;CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch 是不能够重用的,而 CyclicBarrier 是可以重用的。CountDownLatch 的用法:

public class Test {public static void main(String[] args) {

final CountDownLatch latch = new CountDownLatch(2);

     new Thread(){
           public void run() {
               try {
                   System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");                Thread.sleep(3000);                System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");                latch.cou
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值