文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中多线程面试题有哪些

2023-06-14 08:53

关注

这篇文章将为大家详细讲解有关Java中多线程面试题有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

进程和线程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是从一个进程从创建、运行到消亡的过程。在Java中,当我们启动main函数时其实就是启动了一个JVM的进程,而mian函数所在的线程就是这个进程中的一个线程,称为主线程。

线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程都有自己的程序计数器、虚拟机和本地方法栈,所以系统在产生一个线程,或在各个线程之间切换工作是,负担要比进程小很多,所以线程也称轻量级进程。

Java中多线程面试题有哪些

并发和并行

上下文切换

多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU核心在任意时刻内只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略时为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程属于一次上下文切换。

换句话说,当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

sleep()和wait()

 start()和run()

为什么调用start()方法时会执行run()方法,为什么不能直接调用run()方法?

当我们new一个Thread时,线程进入了新建状态,调用start()方法,会启动一个线程并使线程进入就绪状态,等分到时间片后就可以开始运行了。
start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。
而直接执行run()方法会把run方法当作一个main线程下的普通方法去执行,并不是在某个线程中执行它,所以这不是多线程工作。

synchronized关键字

synchronized关键字是解决多个线程之间访问资源的同步性,可以保证被它修饰的方法或代码块在任意时刻只能有一个线程执行。

synchronized主要的三种使用方式:

修饰实例方法

作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。

修饰静态方法

给当前类加锁,会作用于类的所有对象实例,因为静态成员是类成员,不属于任何一个实例对象,所以线程A调用一个实例对象的非静态synchronized方法,而线程B调用该实例对象所属类的静态synchronized方法时是允许的,不会冲突互斥。因为访问静态synchronized方法占用的是当前类的锁,而访问非静态synchronized方法占用的是当前实例对象锁。

修饰代码块

指定加锁对象,进入同步代码库前要获得给定对象的锁。

synchronized和ReentrantLock:

两者都是可重入锁
即自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁时还是可以获取的。

前者依赖JVM而后者依赖API
synchronized是依赖于JVM实现的,ReentrantLock是依赖于JDK层面实现的。

ReentrantLock比synchronized功能多
ReentrantLock增加了一些高级功能,主要说有三点:①等待可中断②可实现公平锁③可实现选择性通知。

volatile关键字

当前Java内存模型下,线程可以把变量保存到本地内存(如寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还在继续使用它在寄存器中变量值的拷贝,造成数据的不一致。

volatile关键字就是解决这个问题,指示JVM这个变量不稳定,每次使用它都要到主存中进行读取。除此之外还有一个重要的作用是防重排。

并发执行的三个重要特性:

原子性

要么所有的操作都得到执行并且不会收到任何因素干扰而中断,要么所有的操作都不执行。可使用synchronized来保证代码原子性。

可见性
当对一个共享变量进行了修改后,那么另外的线程都是立即可以看到修改后的最新值。volatile可以保证可见性。

有序性
代码在执行过程中的先后顺序,Java在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序,即指令重排。volatile可以禁止指令重排优化。

ThreadLocal

通常情况下,我们创建的变量时可以被任何一个线程访问并修改的。如果要实现每一个线程都有自己的专属本地变量该如何解决?这就需要ThreadLocal类了。

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

当创建一个ThreadLocal变量后,访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal名称的由来。可以使用get()和set()方法来获取默认值或将其值更改为当前线程所存副本的值,从而避免了线程安全问题。

实际上,ThreadLocal类有一个静态内部类ThreadLocalMap,可以把ThreadLocalMap看作是ThreadLocal类定制的HashMap,最终的变量是放在了当前线程的ThreadLocalMap中,而不是ThreadLocal类上,可以看作ThreadLocal类是ThreadLocalMap的封装,传递了值。

如果再同一个线程中声明了两个ThradLocal对象的话,会使用Thread内部仅有的那个ThreadLocalMap存放数据的,TheadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调用set方法设置的值。

ThreadLocalMap使用的key为ThreadLocal的弱引用,而value是强引用。所以ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而value不会被
清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时会清理掉key为null 的记录。使用完ThreadLocal方法后最好手动调用remove()方法。

(插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/

线程池

池化技术大家应该很熟悉,线程池、数据库连接池、Http连接池等等都是对这思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
使用线程池,可以降低资源消耗、提高响应速度、提高线程的可管理性。

Runnable和Callable

Runnable接口不会返回结果或抛出检查异常,但Callable接口可以。
工具类Excutors可以实现Runnable对对象和Callable对象之间的相互转换。

@FunctionalInterfacepublic interface Runnable{ //没有返回值也无法抛出异常 public abstract void run();}
@FunctionalInterfacepublic interface Callable<V>{ //@return 计算得出结果 //@throws 如果无法计算结果,则抛出异常}

execute()和submit()

  1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否。

  2. submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型对象,通过这个对象可以判断任务是否执行成功。

 创建线程池

  1. 通过构造器ThreadpoolExecutor实现(下面介绍)。

  2. 通过工具类Executors实现(不推荐)

ThreadPoolExecutor: .

ThreadPoolExecutor

①ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException 来拒绝新任务的处理。
②ThreadPoolExecutor.CallerRunsPolicy:调⽤执⾏⾃⼰的线程运⾏任务。会降低对于新任务提交速度,影响程序的整体性能,另外会增加队列容量。
③ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
④ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

Demo

模拟了 10 个任务,我们配置的核⼼线程数为 5 、等待队列容量为 100 ,所以每次只能存在5个任务同时执⾏,剩下的5个任务会被放到等待队列中去。当前的 5 个任务之⾏完成后,才会之⾏剩下的 5 个任务。

public class MyRunnable implements Runnable { private String command; public MyRunnable(String s) {  this.command = s; } @Override public void run() {  System.out.println(Thread.currentThread().getName() + "开始时间:" + new Date());    processCommand();  System.out.println(Thread.currentThread().getName() + "结束时间:" + new Date()); } private void processCommand() {  try {   Thread.sleep(3000); //设花费3秒执行任务  } catch (InterruptedException e) {   e.printStackTrace();  } } @Override public String toString() {  return this.command; }}
public class Demo { private static final int CORE_POOL_SIZE = 5;//核⼼线程数为 5 private static final int MAX_POOL_SIZE = 10;//最⼤线程数 10 private static final int QUEUE_CAPACITY = 100;//容量100 private static final Long KEEP_ALIVE_TIME = 1L;//等待时间为 1L public static void main(String[] args) {  //通过ThreadPoolExecutor构造函数⾃定义参数创建  ThreadPoolExecutor executor = new ThreadPoolExecutor(    CORE_POOL_SIZE,    MAX_POOL_SIZE,    KEEP_ALIVE_TIME,    TimeUnit.SECONDS,    new ArrayBlockingQueue<>(QUEUE_CAPACITY),    new ThreadPoolExecutor.CallerRunsPolicy());//饱和策略  for (int i = 0; i < 10; i++) {   //创建WorkerThread对象(WorkerThread类实现了Runnable接⼝)   Runnable worker = new MyRunnable("" + i);   executor.execute(worker);//执⾏Runnable  }  executor.shutdown();//终⽌线程池  while (!executor.isTerminated()) {  }  System.out.println("结束"); }}

关于“Java中多线程面试题有哪些”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯