Java高级开发面试题整理

一、并发编程

1、什么是进程和线程?

进程是指程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序就是一个进程创建、运行、到销毁的过程;一个进程可以有多个线程。比如我跑一个java的main方法,系统就创建了一个java进程,这个main方法所在的线程就是这个进程的一个线程,也称为主线程。

2、java实现线程安全的几种方式

1)、使用synchronized关键字,直接修饰方法或编写同步代码块来保证线程安全,建议类内部实现一个object对象锁,不使用“this”,可避免极端死锁情况

2)、Lock可重入锁(ReentrantLock)

3)、使用concurrent包下的集合或是原子性对象包括 AtomicInteger,AtomicLong,AtomicBoolean等

总结:synchronized是jvm层面的,并发较低的情况下推荐使用,并发较高时推荐使用Lock

3、简述多线程的作用

1)、多线程可以提升系统的并发能力及性能,更好的利用服务器资源来提高程序的运行速度及执行效率。

4、线程的生命周期 ?

1)、NEW:初始状态,线程被创建,但是没有被调用start()

2)、RUNABLE:运行状态,线程被调用start()方法,等待运行

3)、BLOCKED:阻塞状态,需要等待锁释放

4)、WAITING:等待状态,该线程需要等待其他线程做出特定动作(通知或中断)

5)、TIMED_WAITING:超时等待状态,可以在指定时间后返回,而不是想WAITING那样一直等待

6)、TERMINATED:终止,表示该线程已经执行完毕

Java高级开发面试题整理_第1张图片

4、悲观锁与乐观锁

悲观锁是假设最坏的情况,认为每次访问共享资源都会出现问题,比如共享数据被修改;所以在每次获取资源的时候就上锁,其他线程获取则被阻塞需要等待,直到线程结束才释放资源;所以悲观锁适用多写的场景。

乐观锁是假设共享资源每次访问不会出现问题,只在修改共享资源的时候判断该资源是否被其他线程修改,若没有则修改成功,有则修改失败;实现乐观锁最常见的方式就是使用版本号机制,修改数据的同时带上版本号条件,检验版本号是否有变化来判断数据是否被其他线程修改。

5、synchronized关键字

它是java语言的一个关键字,代表同步的意思,被它修饰的方法或代码块在同一时刻只能有一个线程执行;一些访问量较高的接口或方法且需要保证唯一性的场景(单机部署的情况下),可以使用该关键字来解决并发问题,比如代码生成自增ID。

值得注意的是,静态synchronized方法与实例synchronized方法之间的调用是非互斥的,因为前者是当前类的锁,后者是当前类的实例对象锁。

6、ThreadLocal

JDK自带的ThreadLocal类是为了让每个线程拥有自己的数据空间,通常我们定义的成员变量,每个线程都是可以修改的,而ThreadLocal存放的只有当前线程自己的值。

其内部有一个ThreadLocalMap,相当于每个线程拥有一个自己的ThreadLocalMap,来存放自己的数据,从而实现线程与线程之间的数据隔离。

注意:使用完ThreadLocal后记得手动调用remove()方法,避免出现内存泄露

7、为什么要用线程池?

1)、降低资源的消耗:避免频繁的创建和销毁线程造成系统资源的消耗

2)、提高响应速度:当程序需要使用线程时,直接从线程池中获取创建好的线程,响应更快

3)、线程可集中管理:统一管理,控制风险,线程不能无限制的创建

8、用过 CountDownLatch 么?什么场景下用的?

CountDownLatch类用来实现多线程的情况下,阻塞主线程等待count个子线程执行完毕再继续往下执行的目的。比如我们使用10个线程同时读取10个文件,现在需要等待10个文件都读取完毕再执行某个操作就要用到CountDownLatch来实现了,伪代码如下:

public class CountDownLatchExample1 {
    // 处理文件的数量
    private static final int threadCount = 10;

    public static void main(String[] args) throws InterruptedException {
        // 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建)
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            final int threadnum = i;
            threadPool.execute(() -> {
                try {
                    //处理文件的业务操作
                    //......
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //表示一个文件已经被完成
                    countDownLatch.countDown();
                }
            });
        }
        //线程阻塞,直至count=0才结束等待
        countDownLatch.await();
        threadPool.shutdown();
        System.out.println("ok");
    }
}

9、如何实现分布式锁

1)、使用redisson实现,基于redis单线程特性,通过SETNX命令实现

2)、使用zookeeper实现,使用它的create()方法来创建一个临时节点,判断这个节点是否存在来实现锁机制

3)、使用Lock实现,创建一个可重入锁的实例将其存储在所有线程可共享的资源中,调用lock()方法上锁,unlock()方式释放锁

10、java中的队列

Java高级开发面试题整理_第2张图片

1)、阻塞队列

所谓的阻塞队列是指当队列是空的,获取元素的线程会被挂起直到其他线程往队列添加新的元素,反之当队列是满的添加元素的线程会被挂起直到队列被移除一个或多个元素或被清空;所谓阻塞,在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒。

BlockingQueue接口的核心方法:

boolean offer(E e):将元素添加到队列中,返回是否添加成功,e的值不能为null否则抛空指针异常

boolean offer(E e, long timeout, TimeUnit unit):将元素添加到队列中,如果队列已满,可指定等待时长,返回是否添加成功

boolean add(E e):将元素添加到队列中,添加成功返回true否则抛出异常,如果限制队列长度推荐使用offer()方法

put(E e):将元素添加到队列中,如果队列已满该方法会一直阻塞,直到队友有空间添加成功

E take():从队列中获取值,如果队列为空,该方法会一直阻塞直到有值并取到值

E poll(long timeout, TimeUnit unit):获取并移除队列的头元素,如果队列为空,可指定等待时长,等待超时则返回null

int remainingCapacity():获取队列中剩余的空间

remove(E e): 从队列中移除指定的值

contains(Object o): 判断队列中是否拥有该值。
drainTo(Collection c):将队列中值,全部移除,并发设置到给定的集合中
实现类

ArrayBlockingQueue:数组结构有界阻塞队列

LinkedBlockingQueue:链表结构组成的有界(默认大小是int最大值)阻塞队列

SynchronousQueue:单个元素队列,只存一个元素;如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素

PriorityBlockingQueue:不一定是先进先出,支持优先级的无界阻塞队列,可通过实现排序接口指定元素排序规则,无界队列会自动扩容,所有put方法永远不会阻塞

DelayQueue:不一定是先进先出,支持延迟功能的无界阻塞队列,可设置任务延迟多久后执行

二、JVM

Java高级开发面试题整理_第3张图片

1、JVM重要的四个组成部分,Class Loader(类加载器)、Runtime Data Area(运行时数据区)、Execution Engine(执行引擎也称解释器)、Native Interface(本地接口)。

类加载器负责将编译后的Class文件加载到运行时数据区,然后由执行引擎将字节码翻译成指定系统指令集交给CPU去执行,在这个过程中会需要调用到不同语言为Java提供的接口,这些接口就是本地接口和本地库了。

2、Runtime Data Area(运行时数据区)

Runtime Data Area(运行时数据区)是用来存放数据的,其中包含:

Stack(栈):主要存储基本数据类型和引用类型变量,每个线程都有自己的栈空间,所以线程之间数据是不能共享的

Heap(堆):主要存储对象实例,由JVM动态分配内存空间,线程之间数据共享

例如:int a = 3; 其中a是对象引用存放在栈空间,“3”是基本类型的值也存放在栈空间

Method Area(方法区):用来存储已被虚拟机加载的类信息、常量、静态变量、编译后的代码等。JDK8之后,方法区存在于元空间

PC Register(程序计数器):每个线程都有各自独立的计数器,可以看做是字节码指令执行的行号指示器,记录了当前正在执行的字节码指令地址,如果执行的是Native方法,则程序计数器为空(Undefined)

Native Method Stack(本地方法栈):本地方法栈与虚拟机栈相似,它服务于本地方法

三、JMM(java内存模型)

1、作用

内存模型描述了程序中各个变量之间的关系,以及在程序实际运行中对变量的存储、读取、修改等操作的底层细节,它围绕有原子性、可见性、有序性三个特征来建立模型。

2、结构规范

JMM规定了所有变量都储存在主内存(Main Memory)中;每个工作线程都有自己的工作内存(Working Memory),工作内存中保存了工作线程需要用到的变量的主内存的副本,线程对变量的所有操作都必须在工作内存中完成,不能直接读写主内存中的变量(volatile关键字修饰的变量也有副本),工作线程的工作内存是互相不访问的,访问其他线程中的变量需要通过主内存来完成

你可能感兴趣的:(面试题,java,java面试题,java基础,java进阶,java高级)