java之多线程面试题总结

本文详细总结了Java多线程面试中常见的问题,包括进程与线程的区别、volatile特性和使用、线程状态、sleep()和wait()的区别、线程创建方式、线程池的工作原理和拒绝策略、synchronized的理解、锁机制(悲观锁和乐观锁)、线程间通信、AtomicInteger的使用以及死锁、阻塞队列和ConcurrentHashMap的底层原理等关键知识点。通过对这些内容的了解,可以帮助开发者更好地掌握Java多线程编程和面试准备。
摘要由CSDN通过智能技术生成

一,进程和线程的联系和区别

参考链接:https://blog.csdn.net/zhou753099943/article/details/51771220
1,两者的概念
      a,进程:是操作系统进行资源分配的基本单位,并能在系统中独立运行;
      b,线程:是操作系统进行资源调度的基本单位。
2,区别
      a,调度:cpu调度和分派的基本单位是线程,而进程作为资源分配的基本单位;如内存就是所谓的资源
      b,归属:一个线程只能属于一个进程,但是一个进程可以包含多个线程;
      c,创建和销毁:在linux环境下,线程的创建使用fork或者vfork指令,进程使用pthread_create指令进行创建。一个进程的结束,那么进程下面的所有线程都将被销毁。一个线程的结束不会影响其他线程的结束;
      d,代价:创建线程的代价比创建进程的代价小很多;

二,java多线程情况下会出现哪些问题

1,常见的问题
      a,死锁问题
      b,数据一致性的问题

三,讲一下volatile

1,volatile概念
      volatile是一种轻量级的同步机制,
      其中的轻量级指的是不需要通过加锁的方式。
      其中同步指的是同一时刻只能有一个线程访问资源,也就是线程安全
2,volatile的特点
      a,可见性(内存一致性):如果一个线程对volatile修饰的变量做了修改,那么其他使用改变量的线程马上可以得知这个修改;
      b,禁止指令重排序:在volatile修饰的变量之前的代码不能重排序到该变量之后,之后的代码不能重排序到该变量之前;
      c,不具有原子性:也就是说在多线程并发下,操作被volatile修饰的变量,还是引起线程不安全的问题;典型就是i++的问题,10条线程分别操作i++100次,结果并不是1000而是小于1000的;
3,可见性的底层实现原理
      写操作:拥有被volatile修饰变量的线程,会监控总线,监控变量是否发生修改,若监控到变量发生修改,那么将内存中变量置为无效;
      读操作:判断内存中的变量是否有效,如果无效,那么就重新在主存中获取;如果有效的,那么直接使用;
4,禁止指令重排序的实现原理
      a,在每个volatile写操作的前面插入一个StoreStore屏障。StoreStore屏障保证在volatile写之前,保证所有的普通写操作已经任意处理器可见;
      b,在每个volatile写操作的后面插入一个StoreLoad屏障。StoreLoad屏障作用volatile写与后面可能有的volatile读/写操作重排序;
      c,在每个volatile读操作的后面插入一个LoadLoad屏障。LoadLoad屏障作用避免volatile读与下面的普通读重排序。
      d,在每个volatile读操作的后面插入一个LoadStore屏障。LoadStore屏障作用避免volatile读与下面的普通写操作重排序。

五,线程的状态

在这里插入图片描述

六,sleep和wait区别

1,sleep()和wait()作用
      a,sleep():让线程睡眠,睡眠时间到了,就到runnable状态;
      b,wait():让线程等待,等待被唤醒;
2,区别
      a,归属:sleep()是Thread类下面的静态方法,wait()是object()下面的实例方法
      b,使用位置:sleep是静态方法可以使用在任意一个位置,wait()方法是实例方法,只能 用在同步代码块和同步方法当中;
      c,释放锁资源:sleep()方法不会释放锁资源,wait()方法的执行会释放锁资源;
      d,作用:sleep()使得线程睡眠进入到timed_waiting状态,wait()使得线程进入到waiting状态
3,注意
      sleep()和wait()方法在执行期间,不会占据cpu的资源;

七,创建线程的几种方式?run()和call()的区别

1,三种方式
      a,自定义一个类,该类实现Runnable接口,然后重写run(),创建自定义类的对象,然后创建Thread类的对象,将自定义类的对象Thread类的参数,然后调用Thread类对象的start();
      b,自定义一个类,该类继承Thread类,然后创建自定义类的对象,调用自定义类的start()。
      c,自定义一个类,该类实现Callable接口,然后重写call(),然后创建自定义类的实例,使用线程池来执行自定类对象

1,自定义个一个类MyCall实现Callable接口,重写了call()
    MyCall<Integer> mc = new MyCall<>();
    ExecutorService executorService = Executors.newFixedThreadPool(线程数目);
2,调用submit(),得到Future对象;
    Future<Integer> result = executorService.submit(mc);
3,使用Future对象得到多线程执行的结果
    int num = result.get();

2,call()和run()的区别
      a,返回值:call()有返回值,是一个
int类型;run()方法没有返回值;
      b,使用上:call()需要和线程池结合使用,run()需要和Thread类的实例结合使用;
      c,归属上:call()属于Callable接口下面的方法,run()属于Runnable接口下面的方法;

九,多线程环境下会出现什么问题

1,死锁问题:多线下程由于相互争夺资源,而造成相互等待的现象
2,一致性问题:也就是数据不一致问题;比如10个线程,每个线程进行i++操作10次,那么我们理想的结果是100,但实际情况往往会小于100;

十,多线程下,如果对一个数进行叠加要怎么做?

1,加锁,通过synchronized或者ReentrantLock实现

2,通过Atomic原子类实现;

十一,为什么要使用线程池?线程池的实现原理?拒绝策略?常用的线程池有哪些?

1,使用线程池的好处
      a,可以减少资源的消耗,避免了频繁的创建和销毁线程,因此减少了资源的消耗
      b,提高了响应速度,使用已有的线程来处理,因此省去了线程创建的步骤,那么就提高响应速度;
      c,增加了线程管理性,线程的创建,销毁,分配做了管理,因此不需要我们自己去管理线程,因此提高了线程的管理性;
2,线程池的实现原理
      第一步:判断corePool核心线程池是否已经满了,若没有满的话,那么直接创建线程,进行处理,如果满了的话,那么就执行第二步
      第二步:判断workQueue队里是否满了,若没有满那么直接将线程处理的任务加入到workQueue中,如果工作队列满了的话,那么执行第三步
      第三步:判断当前的线程池中的线程数是否超过最大线程数,若果没有超过,直接创建线程进行处理,若超过了,那么执行第四步
      第四步:直接使用预先设定好的饱和策略来处理
3,线程池中的参数
      a,int corePoolSize:该参数规定了核心线程池所能拥有线程池数量;
      b,int maximumPool:该参数规定了整个线程池所能拥有线程的数量;
      c,int keepAliveTime:该参数规定了线程存活的时间,如果线程的空闲时间超过了该参数,那么就会被销毁;
      d,int timeunit:该参数规定了线程存活时间单位
      e,BlockingQueue workQueue:该参数用于存储待处理的线程任务
      f,RejectMethod:饱和策略,当线程池中的线程数量已经等于规定的上限,此时接受的线程任务就会被执行该策略;
      g,ThreadFactory threadFactory:用于给线程起一个有意义的名字;
4,拒绝策略
      a,AbortPolicy:该策略为默认策略,直接抛出拒绝执行异常RejectedExecutionException.
      b,CallerRunPolicy(呼叫者政策):使用主线程来处理任务

 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
    if (!e.isShutdown()) {
   
        r.run();
    }
}

      c,DiscardOldestPolicy(丢弃最老的策略):丢弃队列里的对头任务,然后调用线程池来处理当前任务;

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
    if (!e.isShutdown()) {
   
        e.getQueue().poll();
        e.execute(r);
    }
}

      d,DiscardPolicy(丢弃策略):不进行处理,直接丢弃掉,也不会抛出异常;

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
}

5,常用的线程池
参考链接:https://blog.csdn.net/hnd978142833/article/details/80253784
      a,newFixedThreadPool特点
            1,corePoolSize和maximumPoolSize的大小又传入的参数决定,并且两者的大小是一样的;
            2,阻塞队列是LinkedBlockingQueue,为无界阻塞队列

public static ExecutorService newFixedThreadPool(int nThreads) {
   
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

      b,newSingleThreadExecutor特点
            1,corePoolsize和maximumPoolSize均为1;
            2,阻塞队列是LinkedBlockingQueue,为无界阻塞队列;那么就会maximumPoolSize,
keepAliveTime,rejectMethod为无效参数

public static ExecutorService newSingleThreadExecutor() {
   
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS, 
                                new LinkedBlockingQueue<Runnable>()));
}

      c,newCachedThreadPool特点
            1,corePoolSize大小1,maximum大小Integer.MAX_VALUE
            2,阻塞队列为synchronousQueue,该队列为没有容量,一次只能处理一个任务

public static 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值