最全面试官突击一问,【备战秋招冲击大厂,java面试题及其答案

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

+ 避免java特性中的单继承限制
+ 更能体现java面向对象的设计特点

 (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

 (2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

 (3)调用线程对象的start()方法来启动该线程。

package com.thread;

public class RunnableThreadTest implements Runnable{

    private int i;

    public void run(){

        for(i = 0;i <100;i++){

            System.out.println(Thread.currentThread().getName()+" "+i);

        }

    }

    public static void main(String[] args) {

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

            System.out.println(Thread.currentThread().getName()+" "+i);

            if(i==20){

                RunnableThreadTest rtt = new RunnableThreadTest();

                new Thread(rtt,"新线程1").start();

                new Thread(rtt,"新线程2").start();

            }

        }

    }

} 


  • 通过Callable和Future创建线程

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值


package com.thread;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

 

public class CallableThreadTest implements Callable<Integer>{

     public static void main(String[] args) {

        CallableThreadTest ctt = new CallableThreadTest();

        FutureTask<Integer> ft = new FutureTask<>(ctt);

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

            System.out.println(Thread.currentThread().getName()+" 的变量i的值"+i);

            if(i==20) {

                new Thread(ft,"有返回值的线程").start();

            }

        }

        try{

            System.out.println("子线程的返回值:"+ft.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

 

    @Override

    public Integer call() throws Exception{

        int i = 0;

        for(;i<100;i++){

            System.out.println(Thread.currentThread().getName()+" "+i);

        }

        return i;

    }

} 


9. cyclicbarrier和countdownlatch的区别
  • CountDownLatch和CyclicBarrier都能够实现线程之间的等待
  • CountDownLatch一般用于某个线程A或多个线程,等待若干个其他线程执行完任务之后,它才执行;CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量;调用await()方法的线程会被阻塞,直到计数器减到 0 的时候,才能继续往下执行;调用了await()进行阻塞等待的线程,它们阻塞在Latch门闩/栅栏上;只有当条件满足的时候(countDown() N次,将计数减为0),它们才能同时通过这个栅栏;以此能够实现,让所有的线程站在一个起跑线上。
  • CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是减计数,计数减为0后不能重用;而CyclicBarrier是加计数,可置0后复用。
10. Java中提供的线程池

Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor

1)newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)

2)newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)

3)newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

4)newScheduledThreadPool:适用于执行延时或者周期性任务。

11. 自定义线程池(ThreadPoolExector)

new ThreadPoolExecutor(

                    2,

                    9,

                    1L,

                    TimeUnit.SECONDS,

                    new LinkedBlockingDeque<Runnable>(10),

                    Executors.defaultThreadFactory(),

                    new ThreadPoolExecutor.DiscardPolicy()); 


七大参数:

1)corePoolSize(常驻核心线程数):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2)maximumPoolSize(线程池最大线程数):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3)keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4)unit(时间单位)

5)workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

6)threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

  • 四种拒绝策略:

1)ThreadPoolExecutor.AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

2)ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务

4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

  • 配置线程池

1)CPU密集型任务:尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

2)IO密集型任务:可以使用稍大的线程池,一般为2*CPU核心数。IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。CPU核心线程数/1-阻塞系数(0.8~0.9)

3)混合型任务:可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。

12. 线程池的状态

  • RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  • TIDYING(整理状态):所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。
  • TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

更多Java学习资料、面试真题获得,请【点击此处

13. 线程池中shutdown()和shutdownNow()方法的区别

shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。

14. 线程池中 submit() 和 execute() 方法的区别
  • execute():只能执行 Runnable 类型的任务。
  • submit():可以执行 Runnable 和 Callable 类型的任务。
  • submit()能获取返回值(异步)以及处理Exception。
15. ThreadLocal

https://segmentfault.com/a/1190000037728236?utm_source=tag-newest

ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

  • ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是:
  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  • 应用场景
  • 数据库连接池的实现:获取connection
  • 提升性能和安全,如SimpleDateFormat
  • 底层实现

1)ThreadLocal仅仅是个变量访问的入口;

2)每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;

3)ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法时通过threadLocal实例就可以找到绑定在当前线程上的副本对象。

  • 每个线程都有一个属于自己的ThreadLocalMap类, 他用于关联多个以ThreadLocal对象为key, 以你的数据为value的Entry对象, 且该对象的key是一个弱引用对象,以防止内存泄漏。

16. Synchronized在静态方法和非静态方法的区别
  • Synchronzied 修饰非静态方法==》对象锁(同步代码块,方法锁)
  • Synchronzied 修饰静态方法==》类锁,因为是静态方法,它把整个类锁起来了;
17. Synchronized和Lock
  • 实现层面不一样。synchronized 是Java关键字,JVM层面实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
  • 是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally{}代码块显式地中释放锁
  • 是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
  • 获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
  • 功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率
18. Synchronized和Volatile
  • synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
  • synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,禁止指令重排序,无法保证原子性。
  • synchronized 线程阻塞,volatile 线程不阻塞。
  • volatile 本质是告诉jvm当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到,该变量其他线程被阻塞。
  • volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

总结

对于面试还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等)

image

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

…(img-ViOyoz19-1715618457176)]

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值