Java八股文(Java多线程面试题)

并行和并发的区别?

(1)并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生;

(2)并行是在不同实体上的多个事件,并发是在同一实体上的多个事件;

线程和进程的区别?

进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。

线程:是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

创建线程的几种方式?

1. 继承Thread类:(1)创建一个继承于Thread类的子类;(2)重写Thread类的run():将此线程要执行的操作声明在run()中;(3)创建子类对象;(4)通过此对象调用start():启动当前线程,调用当前线程的run();

2. 实现Runnable接口:(1)创建一个实现了Runnable接口的类;(2)实现类去重写Runnable中的run()方法;(3)创建实现类的对象;(4)创建Thread类的对象,将实现类的对象作为参数传递到Thread类的构造器中;(5)通过Thread类的对象调用start()。

3. 通过实现Callable接口:(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。然后再创建Callable实现类的对象;(2)使用FutureTask类来包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值;(3)使用FutureTask对象作为Thread对象的target创建并启动新线程;(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

run()和start()有什么区别?

run()方法被称为线程执行体,它的方法体代表了线程需要完成的任务,每个线程都是通过某个特定Thread对象所对应的run()方法来完成操作的。

start()方法用来启动线程。调用start()方法启动线程时,系统会把该run()方法当成线程执行体来处理,这时无需等待run()方法体代码执行完毕,可以直接继续执行下面的代码;这时此线程是处于就绪状态,并没有运行。但如果直接调用线程对象的run()方法,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。

阻塞线程的方式有哪些?

(1)线程调用sleep()方法主动放弃所占用的处理器资源;(2)线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;(3)线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;(4)线程在等待某个通知;(5)程序调用了线程的suspend()方法将该线程挂起。

线程的生命周期

在线程的生命周期中,它要经过新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。

当线程对象调用了start()方法之后,该线程处于就绪状态,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态;当线程数大于处理器数时,会存在多个线程在同一个CPU上轮换的现象。

当一个线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。当发生如下情况时,线程将会进入阻塞状态:(1)线程调用sleep()方法主动放弃所占用的处理器资源;(2)线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;(3)线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;(4)线程在等待某个通知;(5)程序调用了线程的suspend()方法将该线程挂起。

当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态:(1)调用sleep()方法的线程经过了指定时间;(2)线程调用的阻塞式IO方法已经返回;(3)线程成功地获得了试图取得的同步监视器;(4)线程正在等待某个通知时,其他线程发出了一个通知;(5)处于挂起状态的线程被调用了resume()恢复方法。

线程会以如下三种方式结束,结束后就处于死亡状态:(1)run()或call()方法执行完成,线程正常结束;(2)线程抛出一个未捕获的Exception或Error;(3)直接调用该线程的stop()方法来结束该线程。

sleep()和wait()的区别?

sleep()方法让正在执行的线程主动让出cpu,然后cpu就可以去执行其他任务,在sleep指定时间后cpu再回到该线程继续往下执行,sleep方法只让出了cpu,而并不会释放同步资源锁,到时间自动恢复。

wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。wait()方法会放弃对象锁,进入等待队列。

sleep()可以在任何地方使用,而wait()只能在同步方法或同步代码块中使用。

sleep()不会释放锁,而wait()会释放锁,并需要通过notify()/notifyAll()重新获取锁。

如何实现子线程先执行,主线程再执行?

启动子线程后,立即调用该线程的join()方法,则主线程必须等待子线程执行完成后再执行。

如何实现线程同步?

(1)使用Synchronized关键字:被该关键字修饰的方法或语句块会自动被加上内置锁,从而实现同步。

(2)使用wait和notify:wait()方法可以让当前线程释放对象锁并进入阻塞状态;notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

(3)ReentrantLock:ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

(4)使用特殊域变量volatile:volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。

(5)使用可重入锁实现线程同步;

(6)使用阻塞队列实现线程同步;

wait、notify和notifyAll()?

wait()、notify()、notifyAll()用来实现线程通信,这三个方法都不是Thread类中所声明的方法,而是Object类中声明的方法,并且被final修饰,无法被重写。原因是每个对象都拥有锁,所以让当前线程等待某个对象的锁,所以应该通过这个对象来操作。

wait()方法可以让当前线程释放对象锁并进入阻塞状态。

notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了已就绪(将要竞争锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后,才会进入就绪队列,进而等待CPU的调度。反之,当一个线程被wait后,就会进入阻塞队列,等待被唤醒。

synchronized关键字

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

synchronized关键字最主要的三种使用方式:(1)修饰实例方法:对当前对象实例加锁,进入同步代码前要获得当前对象实例的锁;(2)修饰静态方法:对当前类对象加锁,进入同步代码前要获得当前类对象的锁。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员。(3)修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。和synchronized方法一样,synchronized代码块也是锁定当前对象的。

synchronized可以修饰静态方法,但不能修饰静态代码块。

synchronized与Lock的区别

(1)synchronized是Java关键字,在JVM层面实现加锁和解锁;Lock是一个接口,在代码层面实现加锁和解锁。

(2)synchronized可以用在代码块上、方法上;Lock只能写在代码里。

(3)synchronized在代码执行完或出现异常时自动释放锁;Lock不会自动释放锁,需要在finally中显示释放锁。

(4)synchronized会导致线程拿不到锁一直等待;Lock可以设置获取锁失败的超时时间。

(5)synchronized无法得知是否获取锁成功;Lock则可以通过tryLock得知加锁是否成功。

(6)synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平/不公平,并可以细分读写锁以提高效率。

synchronized和ReenTrantLock的区别?

synchronized是关键字,依赖于JVM;ReentrantLock是类,依赖于API;它提供了比synchronized更多更灵活的特性:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)。

volatile关键字

volatile关键字是用来保证有序性和可见性的。当一个变量被定义成volatile之后,它将具备两项特性:(1)保证可见性;(2)禁止指令重排。即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。

虽然volatile能够保证可见性,但它不能保证原子性。volatile变量在各个线程的工作内存中是不存在一致性问题的,但是Java里面的运算操作符并非原子操作,这导致volatile变量的运算在并发下一样是不安全的。

synchronized和volatile的区别?

(1)volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

(2)volatile仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。

(3)volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。

(4)volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

(5)volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

如果不使用synchronized和Lock,如何保证线程安全?

volatile:volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。

乐观锁和悲观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。Java中悲观锁是通过synchronized关键字或Lock接口来实现的。

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型,这样可以提高吞吐量。乐观锁一般会使用版本号机制或者CAS(Compare And Swap)算法实现。

同步器AQS

抽象队列同步器AbstractQueuedSynchronizer(AQS),用来构建锁或者其他同步组件,减少了各功能组件实现的代码量,也解决了在实现同步器时涉及的大量细节问题,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,例如:ReentrantLock可重入锁(支持公平和非公平的方式获取锁);Semaphore计数信号量;ReentrantReadWriteLock读写锁。

AQS的原理是什么?

如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

ThreadLocal

ThreadLocal是线程私有的局部变量存储容器,它用来存储线程私有变量,内部真正存取是一个Map。每个线程可以通过set()和get()存取变量,多线程间无法访问各自的局部变量。只要线程处于活动状态,它所对应的ThreadLocal实例就是可访问的,线程被终止后,它的所有实例将被垃圾收集。ThreadLocal存储的变量属于当前线程。

ThreadLocal经典的使用场景是为每个线程分配一个JDBC连接 Connection,这样就可以保证每个线程的都在各自的Connection上进行数据库的操作。另外ThreadLocal还经常用于管理Session会话,将Session保存在ThreadLocal中,使线程处理多次处理会话时始终是同一个Session。

线程池

系统启动一个新线程的成本是比较高的,在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。线程池提供了一种限制和管理资源(包括执行一个任务)的方式。

使用线程池的好处:(1)降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗;(2)提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行;(3)提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

如何创建线程池?

(1)通过ThreadPoolExecutor 的构造方法实现;

(2)通过Executor 框架的工具类 Executors 来实现。

线程池的工作流程

(1)判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。

(2)判断任务队列是否已满,没满则将新提交的任务添加在工作队列。

(3)判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和(拒绝)策略。

线程池都有哪些状态?

RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务。

SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用shutdown()方法会使线程池进入到该状态。

STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于RUNNING或SHUTDOWN状态时,调用shutdownNow()方法会使线程池进入到该状态。

TIDYING:如果所有的任务都已终止了,有效线程数为0,线程池进入该状态后会调用terminated()方法进入TERMINATED 状态。

TERMINATED:在terminated()方法执行完后进入该状态,默认terminated()方法中什么也没有做。

线程池的拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:(1)AbortPolicy:丢弃任务并抛出异常。(2)DiscardPolicy:丢弃任务,但是不抛出异常。(3)DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。(4)CallerRunsPolicy:由调用线程处理该任务。

线程池的队列大小通常怎么设置?

(1)CPU密集型任务:尽量使用较小的线程池,一般为CPU核心数+1。

(2)IO密集型任务:可以使用稍大的线程池,一般为2*CPU核心数。

(63)混合型任务:可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。

线程池有哪些参数,各个参数的作用是什么?

(1)corePoolSize(核心工作线程数):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize。

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

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

(4)TimeUnit(线程活动保持时间的单位)

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

(6)threadFactory(线程创建工厂):用于创建新线程。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/906.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【java】了解常见集合类

了解常见集合类 一、集合类框架 1、集合类框架结构图 首先我们要对集合类结构有一个大体的认识,所有集合都继承于迭代器,分为单列集合和映射集合,单列集合分为有序可重复和有序不可重复,大概结构如下图所示 2、主要集合类的介…

你真的知道如何系统高效地学习数据结构与算法吗?

文章目录前言:什么是数据结构?什么是算法?学习这个算法需要什么基础?学习的重点在什么地方?一些可以让你事半功倍的学习技巧1.边学边练,适度刷题2.多问、多思考、多互动3.打怪升级学习法4.知识需要沉淀&…

文心一言---中国版的“ChatGPT”狂飙的机会或许要出现了

⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生👨‍🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三…

linux 基础

1.Shell 命令的格式如下:command -options [argument]command: Shell 命令名称。options: 选项,同一种命令可能有不同的选项,不同的选项其实现的功能不同。argument: Shell 命令是可以带参数的,也可以不带参…

【计算机二级Python】综合题目

计算机二级python真题 文章目录计算机二级python真题一、简单应用——明星投票二、综合应用题《评奖学金 两问》一、简单应用——明星投票 描述使用字典和列表型变量完成最有人气的明星的投票数据分析。投票信息由附件里的文件vote.txt给出,一行只有一个明星姓名的投票才是有效…

【BLE 5.3无线MCU CH582】1、初识CH582开发板(开箱)

1、认识板子 优点: (1)引脚全部引出; (2)USB下载程序; (3)TYPE-C接口好评; (4)板载连个两个USB口,都可以供电;…

前端性能优化之HTTP缓存

前端缓存 前端缓存可分为两大类:HTTP 缓存和浏览器缓存。 我们今天重点是 HTTP 缓存,下面这张图是前端缓存的一个大致知识点: HTTP 缓存 首先解决困扰绕人们的老大难问题: 一、什么是HTTP缓存? HTTP 缓存会存储与请…

六个实用技巧让你轻松写出优雅的链表代码

文章目录📕前言:如何轻松写出正确的链表代码?📖技巧一:理解指针或引用的含义📖技巧二:警惕指针丢失和内存泄漏📖技巧三:利用哨兵简化实现难度📖技巧四&#x…

HTTP 协议

文章目录1. 前言2. HTTP 协议3. fiddler 的安装与认识4. HTTP 协议报文格式4.1 请求4.2 响应5. 构造 HTTP 请求5.1 基于 form 表单构造 HTTP 请求5.2 基于 ajax 构造 HTTP 请求6. postman7. HTTPS7.1 加密7.2 HTTPS 的工作过程1. 前言 前面几篇文章 , 说了关于 前端…

C++继承[万字详解]

目录 一.继承的介绍 1.1、继承的概念 1.2、继承的定义 1.2.1、定义格式 1.2.2、继承关系和访问限定符 1.2.3、继承基类成员后,在子类中成员访问方式的变化 二.基类和派生类对象赋值转化 三.继承中的作用域 四.派生类的默认成员函数 ★派生类的构造函数 派…

有关pytorch的一些总结

Tensor 含义 张量(Tensor):是一个多维数组,它是标量、向量、矩阵的高维拓展。 创建 非随机创建 1.用数组创建 将数组转化为tensor np.ones([a,b]) 全为1 #首先导入PyTorch import torch#数组创建 import numpy as np anp.arr…

4.类的基本概念

目录 4.1 类的概述 类是一种活动的数据结构 4.2 程序和类:一个快速实例 4.3 声明类 ​4.4 类成员 4.4.1 字段 1.显示和隐式字段初始化 2. 声明多个字段 4.4.2 方法 4.5 创建变量和类的实例 4.6 为数据分配内存 合并这两个步骤 4.7 实例成员 4.8 访问修饰…

2023年天津市逆向re3.exe解析(超详细)

2023年天津市逆向re3.exe解析 1.拖进IDA里进行分析2.动态调试(过程省略了)3.解密加密算法4.输入FLAG 回显成功!1.拖进IDA里进行分析 打开后是这么一个程序,直接找到main函数f5反编译即可,这里要注意程序第一次反编译出的代码会有点问题,需要点进引用的那些其他sub函数里面…

【面试题】闭包是什么?this 到底指向谁?

一通百通,其实函数执行上下文、作用域链、闭包、this、箭头函数是相互关联的,他们的特性并不是孤立的,而是相通的。因为内部函数可以访问外层函数的变量,所以才有了闭包的现象。箭头函数内没有 this 和 arguments,所以…

百度将?百度已!

仿佛一夜之间,创业公司OpenAI旗下的ChatGPT就火遍全球。这是一场十分罕见的科技盛宴。下到普通用户,上到各科技大厂都在讨论ChatGPT的前景,国外的微软、谷歌,国内的百度、腾讯、阿里等等都在布局相关业务。比尔盖茨更是称ChatGPT与…

OSPF*MGRE实验:

OSPF*MGRE 一:实验拓扑 实验目的: IP地址配置如拓扑所示; 路由器配置: ospf-10 [r1]ospf 10 [r1-ospf-10]area 0 [r1-ospf-10-area-0.0.0.0]network 192.168.1.1 0.0.0.0 [r2]ospf 10 [r2-ospf-10]area 10 [r2-ospf-10-area-0.0.…

十大经典排序算法(下)

🍓个人主页:bit.. 🍒系列专栏:Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 HTML和CSS3 目录 1.6 快速排序 1. 算法步骤 2. 动图演示 3.代码实现 1.7 堆排序 1. 算法步骤 2. 动图演示 3. 代码实现 1.8 计数排…

2023-03-17:使用Go语言和FFmpeg库实现音频重采样解码,并将其保存为PCM格式的文件。

2023-03-17:使用Go语言和FFmpeg库实现音频重采样解码,并将其保存为PCM格式的文件。 答案2023-03-17: 在音视频处理领域,常常需要对音频进行重采样和解码,以便于后续的处理和分析。本文将介绍如何使用Go语言及FFmpeg库…

【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数

前言:在之前,我们对类和对象的上篇进行了讲解,今天我们我将给大家带来的是类和对象中篇的学习,继续深入探讨【C】中类和对象的相关知识!!! 目录 1. 类的6个默认成员函数 2. 构造函数 2.1概念介…

【黑马JVM(2)】垃圾回收

JVM垃圾回收如何判断对象可以回收引用计数法可达性分析算法四种引用垃圾回收算法标记-清除标记-整理标记-复制分代垃圾回收相关VM参数垃圾回收器串行吞吐量优先响应时间优先G1垃圾回收阶段Young CollectionYoung Collection跨代引用Young CollectionCMRemark-SATBMixed Collect…
最新文章