Java多线程、并发基础面试题

1、线程和进程的区别?

二者对比:

  • 进程是正在运行程序的实力,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不用的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

2、并行和并发有什么区别?

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行
  • 并行:在同一时刻,有多个指令在多个CPU上同时执行

3、创建线程的方式有哪些?

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池创建线程(项目中常用方式)

4、Runnable和Callable有什么区别?

  1. Runnable接口run方法没有返回值
  2. Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  3. Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能上抛。

5、线程的run()和start()有什么区别?

  • start():用来启动线程,通过该线程调用run()方法执行run方法中所定义的逻辑代码,start方法只能被调用一次。
  • run():封装了要被线程执行的代码,可以被调用多次。

6、线程包括哪些状态?

新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、时间等待(TIMED_WALTING)、终止(TERMINATED)

7、线程状态之间是如何变化的?

Java多线程、并发基础面试题_第1张图片

  • 创建线程对象是新建状态
  • 调用了start()方法转变为可执行状态
  • 线程获取到了CPU的执行权,执行结束是终止状态
  • 在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态
    • 如果没有获取锁进入阻塞状态,获得锁再切换为可执行状态
    • 如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
    • 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态

8、新建T1、T2、T3三个线程,如何保证它们按顺序执行?

可以使用线程中的join方法解决

join()        等待线程运行结束

public class ThreadTest {
    public static void main(String[] args) {
        //创建线程1
        Thread t1 = new Thread(()->{
            System.out.println("t1");
        });

        //创建线程2
        Thread t2 = new Thread(() -> {
            try {
                //加入线程t1,只有t1线程执行完毕之后,才会执行此线程
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        });
        //创建线程3
        Thread t3 = new Thread(()->{
            try {
                //加入t2线程,只有当t2线程执行完毕之后,才会执行这个t3线程
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        });

        //启动线程
        t1.start();
        t3.start();
        t2.start();
    }
}

9、notify()和notifyAll()有什么区别?

  • notifyAll:唤醒所有wait的线程
  • notify:只随机唤醒一个wait线程

10、在Java中wait和sleep方法的不同?

共同点:

wait()、wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态

不同点:

1)方法归属不同:

  • sleep(long)是Thread的静态方法
  • 而wait()、wait(long)都是Object的成员方法,每个对象都有

2)醒来时机不同:

  • 执行sleep(long)和wait(long)的线程都会在等待响应毫秒后醒来
  • wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去
  • 它们都可以被打断唤醒

3)锁特性不同:

  • wait()方法的调用必须先获取wait对象的锁,而sleep则无此限制
  • wait()方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃了CPU,但你们还可以用)
  • 而sleep()如果在synchronized代码块中执行,并不会释放对象锁(我放弃了CPU,你们也用不了)

11、如何停止一个正在运行的线程?

有三种方式可以停止线程

  • 使用退出标志, 使线程正常退出,也就是当run()方法完成后线程终止
  • 使用stop方法强行终止(不推荐)
  • 使用interrupt方法中断线程
    • 打断阻塞的线程(sleep、wait、join)的线程,线程会抛出异常
    • 打断正常的线程,可以根据打断状态来标记是否退出线程

1、synchronized关键字的底层原理

1)synchronized对象锁采用互斥的方式让同一时刻至多有一个线程能持有对象锁

2)它的底层是由Monitor实现的,Monitor是JVM级别的对象(C++实现),线程获得锁需要使用对象(锁)关联Monitor

3)在Monitor内部有三个属性,分别是owner、entrylist、waitset

  • owner:存储当前获取锁的线程,只能有一个线程可以获取
  • entryList:关联没有抢到锁的线程,处于Blocked状态的线程
  • waitSet:关联调用了wait方法的线程,处于waiting状态的线程

Java多线程、并发基础面试题_第2张图片

2、 谈谈JMM(Java内存模型)

1)JMM指的是Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。

2)JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)

3)线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存

3、你知道什么是CAS吗?

  • CAS全称是Compare And Swap 比较再交换,它体现的是一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性。
  • CAS使用到的地方很多:AQS框架,AtomicXXX类
  • 在操作共享变量的时候使用的自旋锁,效率上更高一些

4、乐观锁和悲观锁的区别?

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃点亏再重试呗
  • synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

5、请你谈谈你对volatile的理解

1)保证线程间的可见性

用volatile修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见

2)进制进行指令重排序

指令重排:用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,组织其他读写操作越过屏障,从而达到阻止重排序的效果。

  • 写变量让volatile修饰的变量的代码在最后位置
  • 读变量让volatile修饰的变量的代码在最开始位置

6、什么是AQS?

1)ASQ是多线程中的队列同步器,是一种锁机制,它是作为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的。

2)AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程

3)在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中有一个线程修改成功了state为1,则当前线程就相当于获取了资源

4)在对state修改的时候使用的cas操作,保证了多个线程修改的情况下的原子性

Java多线程、并发基础面试题_第3张图片

7、AQS是公平锁还是非公平锁? 

既可以是公平锁又可以是非公平锁

  • 新的线程与队列中的线程共同来抢资源,是非公平锁
  • 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁

8、ReentrantLock的实现原理?

  • ReentrantLock表示支持重新进入的锁,调用lock方法获取了锁之后,再次调用lock,是不会再阻塞
  • ReentrantLock主要利用CAS + ASQ队列来实现
  • 支持公平锁和非公平锁,在提供的构造器中无参默认是非公平锁,也可以传参设置为公平锁

9、synchronized和Lock有什么区别?

语法层面:

synchronized是关键字;源码在JVM中,用C++语言实现

Lock是接口,源码由JDK提供,用Java语言实现

使用synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁

功能层面:

二者均属于悲观锁,都具备基本的互斥,同步,锁重入功能

Lock提供了许多synchronized不具备的功能,例如公平锁,可打断,可超时,多条件变量

Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock(读写锁)

性能层面:

在没有竞争时,synchronized做了很多优化,比如偏向锁、轻量级锁、性能不赖

在竞争激烈时,Lock的实现通常会提供更好的性能

10、死锁产生的条件是什么?

一个线程需要同时获取多把锁,这时就容易发生死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都会停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,就有可能发生死锁的问题

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

也就是多个线程互相拿着对方需要的资源,然后形成僵持


11、如何进行死锁诊断?

  • 当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和jstack
  • jps:输出JVM中运行的进程状态信息
  • jstack:查看Java线程内线程的堆栈信息,查看日志,检查是否有死锁,如果有死锁现象,需要查看具体代码分析后,可修复
  • 可视化工具jconsole、VisualVM也可以检查死锁问题

12、聊一下ConcurrentHashMap

1)底层数据结构:

  • JDK1.7底层采用分段的数组 + 链表实现
  • JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组 + 链表/红黑二叉树

2)加锁的方式:

  • JDK1.7采用Segment分段锁,底层使用的是ReentrantLock
  • JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好

13、导致并发程序出现问题的根本原因是什么?

1.原子性        synchronized、lock解决

2.内存可见性        volatile、synchronized、lock解决

3.有序性        volatile解决


1、说一下线程池的执行原理?

Java多线程、并发基础面试题_第4张图片


2、线程池中有哪些常见的阻塞队列?

Java多线程、并发基础面试题_第5张图片

 ArrayBlockQueue和LinkedBlockingQueue的区别?

Java多线程、并发基础面试题_第6张图片


3、如何确定核心线程数?

1)IO密集型任务

一般来说:文件读写、DB读写、网络请求等。核心线程数大小设置为2N+1

2)CPU密集型任务

一般来说:计算型代码,bitmap转换,Gson转换等。核心线程数大小设置为 N+1

N指的是CPU的核数

①、高并发、任务执行时间短----->CPU核数 + 1,减少线程上下文的切换

②、并发不高、任务执行时间长

        1.IO密集型的任务------>CPU核数*2 + 1

        2.计算密集型任务------->CPU核数 + 1

③、并发高、业务执行时间长,解决这种问题的关键不在线程池而在于整体架构的设计的优化,对架构和业务做优化

4、线程池的种类有哪些?

①、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

②、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(先进先出)执行

③、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

④、newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

5、为什么不建议用Executors创建线程池?

参考阿里Java开发手册

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors返回的线程池对象的弊端如下:

1)FixedThreadPoolSingleThreadPool

允许的请求队列长度为 Integer.MAX_VALUE:可能会堆积大量的请求,从而导致OOM

2)CachedThreadPool

允许的创建线程数量为:integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM


6、说一下线程池的核心参数?

  • corePoolSize:核心线程数目
  • maximumPoolSize:最大线程数目(最大线程数 = 核心线程 + 救急线程)
  • keepAliveTime:生存时间
  • unit:时间单位(救急线程的生存时间单位)
  • workQueue:当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  • threadFactory:线程工厂(可以定制线程对象的创建)
  • handler:拒绝策略

你可能感兴趣的:(八股文面试,面试,java,职场和发展,java-ee)