java开发工程师面试题

编程知识 更新时间:2023-05-03 03:16:05

一个数组有n个数,有的出现奇数次,有的出现偶数次,找出奇数次的数?
方式一、遍历数组,开辟一片内存空间存储map集合,键为出现的数据,值为出现的次数。时间复杂度O(n)空间复杂度O(n)
方式二、采用按位异或【相同的数为0,不同的数为1】产生的结果为a异或b,然后将异或的结果右边第一个1置1其他的都置0产生一个数,此数称为rightone,并将rightone与a^b的结果异或得到的结果与rightone按位与则可以得到a或者b其中一个。
什么是存根类
如果业务中需要使用一个接口中的某个或者某些方法,如果直接实现该接口就需要重写所有方法,可以创建一个存根类去实现这些方法【重写的时候直接将方法体置为空】。然后在业务的类中去继承存根类并且实现需要的方法,这样就可以避免实现无关的方法。
String可以被继承么?为什么
不可以,因为String被final修饰符修饰
String、StringBuffer和StringBuilder有什么区别?
相同点:都是final类,不允许被继承/参数修改。 不同点:StringBuffer是线程安全的,可以无需额外的同步就可以运用在多线程中 StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了 String实现了三个接口:Serializable、Comparable、CharSequence StringBuilder只实现了两个接口Serializable、CharSequence,相比之下String的实例可以通过compareTo方法进行比较,其他两个不可以。
多线程的几种实现方式?
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3.通过Callable创建线程
4.通过线程池创建线程
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用 。
start():启动线程,然后由JVM调用此线程的run()方法,结果是两个线程交替的调用run()方法。
java中的反射是一个什么概念?
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态语言的一个关键性质。
反射的优缺点?
一、Java反射的优点
1.增加程序的灵活性,避免将程序写死到代码里。
2.代码简洁,提高代码的复用率,外部调用方便
一、Java反射的缺点
1.性能问题 使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。 反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
2.使用反射会模糊程序内部逻辑 程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
3.安全限制 使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。
4.内部暴露 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
MySQL和SQL Server的区别?
本质区别mysql便宜,mysql性能更占优,数据库与磁盘兼容而不占用过多cpu和内存,可以运行与Windows不发生冲突,在unix运行则更好。而且他有一个二进制日志可以看到你所执行的所有sql语句 sqlserver稳定性更好,但也需要增加额外的复杂操作,增加磁盘存储,内存损耗等。而sqlserver会同步数据在多台服务器上可以实现数据的冗余且将数据库放在内网来保证安全性。
MySQL的优点:
(1)支持5000万条记录的数据仓库;
(2)适应于所有的平台;
(3)是开源软件,版本更新较快;
(4)性能很出色。
(5)价格便宜 MySQL的缺点: 运行速度慢,不够稳定,有掉线的情况.
SQL Server的优点:
1、 扩展性强:当系统要更高数据库处理速度时,只要简单地增加数据库服务器就 可以得到扩展
2、 可维护性:当某节点发生故障时,系统会自动检测故障并转移故障节点的应用,保证数据库的持续工作。
3、安全性:因为数据会同步的多台服务器上,可以实现数据集的冗余,通过多份数据来保证安全性。另外它成功地将数据库放到了内网之中,更好地保护了数据库的安全性。
4、 易用性:对应用来说完全透明,集群暴露出来的就是一个IP
SQL Server的缺点
1、 不能够按照Web服务器的处理能力分配负载。
2、 负载均衡器(控制端)故障,会导致整个数据库系统瘫痪。
还有一些语法上的区别: 例如sqlserver支持check约束而mysql不支持(只能在几个值中选一个,一般与default连用) sqlserver支持nvarchar,nchar,ntext等MySQL不支持(nvarchar的最大长度4000,varchar8000,varchar是肥unicode编码的可变长度类型,nvarchar支持多种语言)
如何实现分库分表?
1、分库分表的原理: 将原本存储于单个数据库或单张表中的数据分别存储到多个数据库或多张表中,用于提升查询效率。
2、实现方式: 垂直切分和水平切分
垂直切分:就是将每个元素部分属性创建另外一张表,例如老师的教的学科可以做一个学科表,根据学科id来查询
水平切分:就是将超过一定数量的元素放到另外一张表或库中
实现sql优化的方式有哪些?
应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引,但即使创建了索引,但由于sql语句可能会使索引失效,所有应该避免一些类似于以下书写sql的方式
1、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,可以为空值设默认值
2、尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
3、in 和 not in 也要慎用可以使用between替代
4、避免在where子句中对字段进行函数操作
索引和主键有什么区别?
1.主键一定是唯一性索引,唯一性索引并不一定就是主键。
2.一个表中可以有多个唯一性索引,但只能有一个主键。
3.主键列不允许空值,而唯一性索引列允许空值。
4.索引可以提高查询的速度。
5.主键和索引都是键,不过主键是逻辑键,索引是物理键,意思就是主键不实际存在,而索引实际存在在数据库中
cookie和session有什么区别?
1、cookie一般存储在客户端浏览器中,session一般存储在服务器中。
2、cookie一般存储少量敏感度较低的数据,因为其数据不怎么安全且大小限制在4kb。而session可以存储任意类型任意大小的数据。
3、cookie默认在浏览器关闭后销毁,而session在服务器关闭后销毁或者到达失效时间后。(要保证服务器关闭后请求的sesion是同一个,需要做session的钝化【将session对象序列化到硬盘上】和活化)
4、默认情况下cookie不能在多个服务器中共享,而session可以。
get和post有什么区别?
1.get是一次请求,post是两次请求
2. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
3. Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
4 Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
5. Get执行效率却比Post方法好。Get是form提交的默认方法。
6.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
数据库的隔离级别和同步问题?
同步问题1:脏读(读取方读取数据,修改方将未提交的数据进行回滚),导致读取到错误信息。
解决方法:将隔离级别从读未提交改为都已提交
同步问题2:不可重复读(针对同一条数据,读取方读取数据,修改方对该数据进行操作,读取方再次查询时会出现两种不同的数据。)
解决方法:将隔离级别从都已提交修改为可重复读。(对事物进行操作时,不能进行修改操作)
同步问题3:幻读(针对一张表,读取方读取数据,修改插入数据,读取方再次读取数据,数据条目数发生变化)
解决方法:将隔离级别从可重复读修改为串行化(在一个事物结束之前,禁止其他事物对该表操作,这样所有并发问题都可以解决)
隔离级别越高,并发问题处理的越好,但是效率越低,所以在实际的应用中会避免读未提交和串行化,会在读已提交和可重复读中择优选择。
抽象类和接口的区别
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用
类和接口实现或继承
类对接口的实现,使用implement关键字,且可以同时实现多个接口。
类对类的继承,使用extend关键字,一个类只能继承一个父类。
接口不能继承类。
接口对接口的“实现”称为继承或者接口的扩展,使用extend关键字,可以同时继承或扩展多个接口。
List/set/map的区别
list和set都是存储单列数据的集合map是存储双列数据的集合
linkedList:基于链表,增删快查找慢
arrayList:基于动态数组,增删慢查找快,线程不安全,效率高
Vector:基于数组,增删慢查找快,线程安全,效率低
Hashset:底层是hashmap,不允许重复,使用该方法时需要重写equals()和hashcode方法
Linkedhashset:链表和hashmap
HashMap基于数组和链表,当链表长度超过8的时候链表转换为红黑树,线程不安全但是高效,支持null值和null键(红黑数的优势,提高增删查的时间复杂度是logn)
LinkedHashMap是hashmap的一个子类,保留插入的顺序
TreeMap基于红黑树(红黑树的优势,提高增删查的时间复杂度是logn),实现sortedMap接口是有序的集合,默认为自然排序的方式,可以指定比较器排序
myBatis的$和#的区别
1:#{}是占位符,${}是拼接符。//占位符相当于JDBC的prepareStatement的?,拼接符相当于拼接字符串,会导致第二条。
2:#{}是预编译可以避免sql注入问题,而$是编译,避免不了sql注入问题。
3:#{} 的变量替换是在DBMS 中, ${}的变量替换是在DBMS 外。
4:#{}会自动为参数加上单引号,${}不会
java中的==和equals的区别
首先讲==:
针对于基本数据类型==比较的是其值,例如整型,如果创建的int型数据i和j其值都为3那么==为true
针对与引用数据类型==比较的是其内存地址【一个内存地址代表一个对象】
所以如果比较的是两个char[],如用char[]创建 chars1和chars2都为{1,2,3},但是其本质不是一个对象,分配的内存空间也不是同一块,所以直接用==比较结果是false
对于equals:
equals,他不支持在基本数据类型中使用,其比较也是比较其地址,看其是否是同一个对象。所以在针对引用数据类型的时候【如果没有重写equals方法】,==和equals基本没区别。
最后就是equals方法可以重写。
消息队列解决了什么问题
一、解耦,如A系统的关键数据通过接口传递给其他系统,当其他系统的需求改变时【突然要或者不要了】就很麻烦,用mq后谁需要谁自己去mq消费,降低系统的耦合性
二、异步,当用户连续发送多个请求时如果同步的通过接口访问会产生极大的延迟,但如果使用了mq,请求异步分配给不同队列执行则会大大降低延迟。
三、削峰,当高峰时期请求量迸发时,数据库请求过多容易使服务器宕机。如果是使用了mq的系统,由系统自动拉取可以处理的请求量可以避免服务器宕机。
消息队列的缺点
一、系统可用性降低,多使用了一个中间件,若mq瘫痪abcd系统全部都不能使用了。
二、系统复杂度提高,需要考虑处理重复信息,消息队列保证有序,处理信息丢失等问题了。
三、数据一致性问题,A系统将数据提交相应成功就认为任务结束了,但bcd系统的任务才开始,要是出问题了数据就会不一致。
各mq的对比

消息队列如何处理重复消息
幂等处理:使消息重复执行对结果造成的影响跟只执行一次的结果一致。例如更新账户余额为100,更新10次余额还是为100。当需求为为账户余额增加100时可以通过数据库的唯一性约束来实现或者为其增加前置条件【当用户id为001且用户余额为100时才增加100,如果这条语句已经执行过了其余额不符合条件就不会重复执行】
消息队列如何保证有序
全局有序:单生产者产生消息给broker,单消费者消费消息
部分有序:多个生产者,单个broker中的多个队列,多个消费者一一对应
消息队列的推拉模式
指的是Consumer和Broker之间的交互。【Producer和Broker之间一般是推】
推模式:broker主动将消息推送给consumer,
优点:实时性高和对于消费者来说简单。
缺点:当消费者的消费速率过低的时候容易爆仓,且如果多个消费者消费速率不一致broker难以平衡消费者的速率
拉模式:consumer向broker拉取消息
优点:对于broker来说简单,且消费者可以根据自己的消费速率来获取消息
缺点:延时较高,如果消息忙请求,这段时间没有消息,消费者的请求就是无效的
长轮询模式
rabbit的长轮询模式就是每隔五秒rabbitmq自动查询一次新消息,还有一个线程监视log如果有新消息则唤醒请求
rabbitmq的高可用
单机模式:demo级别的,练习用
普通集群模式:多台机器上启动rabbitmq且生成带有元数据的queue,但只有主机有真实消息,当其他queue需要消息时从主机拉取,本质上没有做到分布式,且会遇到单实例性能瓶颈
镜像集群模式【高可用模式】:多台机器启动rabbitmq且生成带有元数据和全部消息镜像的queue,并且在你写入消息时会自动同步到所有queue中
如何开启rabbit的镜像集群模式
在管理控制台新增镜像集群模式的策略
镜像集群模式的优缺点
优点:高可用性,当一个服务宕机了,消息在其他queue中有相同镜像直接拉取即可
缺点:性能低,占用内存和磁盘。无法线性扩展queue
如何保证消息不丢失
【生产者发送到broker】
一、使用rabbitmq的事务模式。生产者发送的消息丢失了会收到报错信息,就可以回滚事务。【缺点:吞吐量高,性能降低】
二、使用rabbitmq的confirm模式。发送的消息会生成一个id,当queue收到时会返回ack,没收到就会返回nack。
两者区别,事务是同步的,当没有提交事务就只能阻塞着。但是confirm是异步的,在没有收到ack时还可以发送其他的消息
【rabbitmq自己丢失数据】
开始持久化,第一步创建 queue 的时候将其设置为持久化,但是其只会持久化queue的元数据,消息数据不会。第二步发送消息的时候将消息的 deliveryMode 设置为 2,这是将消息持久化
【消费者丢失消息】
使用rabbitmq提供的ack模式,只有处理完了给queue发送ack了queue才会删除消息。
ElasticSearch分布式搜索引擎
什么叫做集群的故障转移
在es中单个节点的分片的备份没有发挥作用时。【如备份文件被放在本节点中/只有一个节点在工作】如果机器出现物理故障,数据就会丢失。这时只要开启新节点并为节点的各分片备份即可。
死锁产生的四个必要条件

  • 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放;
  • 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放;
  • 不剥夺条件/非抢占式:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放;
  • 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合 {P0,P1,P2,···,Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。
    redis的持久化策略
    有两种:一种RDB一种AOF
    RDB生成一个二级制的.rdb文件,内部存储了各个数据库的键值对等信息。
    持久化过程:分为手动触发和自动触发。手动触发是调用SAVE或BGSAVE命令。自动触发是服务器满足某种条件时自动调用SAVE或BGSAVE指令。
    优势:文件占用资源少,恢复数据速度很快。
    劣势:无法实现实时持久化,因为这个操作比较重量级不适合频繁执行。
    AOF以独立日志的方式生成每次写入的命令,存储协议文本,重启时会重新执行文件中的命令来恢复数据。
    持久化过程:everysec选项,用户可以将数据丢失的时间窗口限制在1秒之内。
    优势:相对于RDB可能丢失大量数据的情况,AOF会安全许多。
    劣势:恢复速度比RDB慢得多,AOF在进行重写时也需要创建子进程,在数据库体积较大时将占用大量资源,会导致服务器的短暂阻塞。
    自己写一个单例模式
    单例模式特点:确保类只有一个实例存在
    单例模式分为饿汉模式和懒汉模式,饿汉模式在类被加载的时候就自动实例化。懒汉模式在类第一次被调用的时候被实例化。但懒汉模式在实例化的时候可能遇到同步问题,所以需要用到锁。

饿汉模式:

public class Singleton{ 
	private static Singleton instance = new Singleton(); // 构造方法私有,确保外界不能直接实例化 
	private Singleton(){ } //通过公有的静态方法获取对象实例 
	public static Singleton getInstance(){ return instance; }
} 

懒汉模式:

public class Singleton{
	private static Singleton instance = null;
	private Singleton(){}
	
	synchronized public static Singleton getInstance(){
		if(instance == null){
			instace=new Singleton();
		}
	}
}

ioc是什么:
控制反转和依赖注入,控制反转将原本由人完成的类的实例化工作交给spring容器去完成,可以降低对象之间的耦合度。实现方式依赖注入。

协程是什么:是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。一个进程可以包含多个线程,一个线程可以包含多个协程。
线程和协程的区别:
1、如果多核cpu的话进程和线程都可以并行运行,但是协程只能串行运行,当一个线程内的某个协程在运行时其他协程必须挂起。
2、进程和线程都是操作系统层面的资源,协程是只是一个特殊函数
3、线程是抢占式的,而协程是非抢占式的,需要用户释放使用权切换到其他协程。

用redis设计一个分布式锁:
setnx key value如果用户忘记解锁就会造成死锁,所以想到给锁添加过期时间⬇
setnx key value + expired key second 由于不是原子操作,如果expired失败依然可能死锁。⬇
set key value nx ex seconds 将两步放在一个原子性操作里即可
但是释放锁不能简单del key,因为如果有进程在过期时间之前结束并释放锁那么redis就会释放其他进程新上的锁,所以要给锁赋一个随机值,以避免误删。

聚族索引和非聚族索引
聚族索引:数据和索引不分离,找到了索引就找到了数据
非聚族索引:数据和索引分离,索引下的数据是主键,多一个回表操作。

线程安全的集合:
绝大部分java.util中的集合都是线程不安全的,vector和hashtable是线程安全的但是这俩都是由synchronized实现的,性能差。一般用collections包中的synchronizedXxx()方法将非线程安全的类包装成线程安全的类。

缓存穿透是什么?解决方法有哪些?
缓存穿透就是误删了数据或恶意攻击,查询缓存和数据库中都不存在的数据,这样每次请求都会去查库,而不会去查缓存。如果同时有大量请求进来,就会给数据库产生巨大压力甚至db宕机。
解决方法一、缓存空对象。当缓存和数据都查不到对应key数据时可以缓存空对象到缓存中,这样下次查询空对象就直接从缓存中返回了。为了避免存储过多空对象,会对空对象设置一个比较短的过期时间。
方法一的问题:如果大量key穿透会导致空对象占用的缓存会占用大量内存空间。
如果空对象在缓存时间内有了数据这时候就会产生数据不一致的问题。
解决方法二、布隆过滤器,优点:节省空间,不需要存储数据本身只需要存储数据对应hash比特位。时间复杂度低,基于hash算法,插入和查找的时间复杂度都为o(k)k为哈希函数的个数。缺点:不是完全准确,准确率取决于哈希函数的个数。布隆过滤器不能删除元素,所以集合中不存在的数可能在过滤器中存在。

缓存击穿是什么?解决方法有哪些?
缓存击穿就是一个热点key在大并发集中访问中,在key过期时间到的一瞬间,大批量并发请求击穿缓存,直接请求数据库,数据库的查询压力激增导致请求堵塞甚至宕机。
解决方法一、不设置失效时间:热点key可以不设置过期时间。如果数据更新可以在后台开启一个异步线程,发现过期的key直接重写缓存。但这个方法只适于数据一致性要求不高的需求。
解决方法二、互斥锁:当 key 失效的时候,让一个线程读取数据并构建到缓存中,其他线程就先等待,直到缓存构建完后重新读取缓存即可。

缓存雪崩是什么?解决方法有哪些?
缓存雪崩就是大量key的缓存击穿。
解决方案一二、可以跟击穿一样,通过不设置失效时间和互斥锁解决。
解决方案三、为预防大面积key同时失效可以给不同的key的过期时间加上随机值,使其大致均匀。
加强互斥锁的可靠性:设置主备缓存策略,主缓存有效时间按经验值设置,主缓存的key失效之后从数据库加载最新值。备份缓存有效期长,只在获取互斥锁失败时读取此缓存,当主缓存更新时同步更新备用缓存。

缓存预热是什么?不同情况应该怎么设置?
系统上线后现将相关数据构建到缓存中,这样就可以避免用户请求的时候直接查库。
根据访问量的大小设置缓存预热:
数据量不大的时候,工程启动的时候进行加载缓存动作,这种数据一般可以是电商首页的运营位之类的信息。
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新。
数据量超大的时候,优先保证热点数据的进行提前加载到缓存,并确保访问期间不能更改缓存,比如秒杀要提前将商品信息之类的数据刷新到缓存并且在秒杀期间不能更改商品属性。

缓存降级
指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或返回服务的内存数据。但是分布式系统中很容易出现数据不一致的问题,所以优先保证服务器的高可用如redis集群部署并做好备份,尽量避免缓存降级。

redis如何与数据库保持双写一致性
得考虑两个问题:
1、先更新数据库还是先更新缓存
2、缓存是选择更新还是删除
更新缓存优势是,不容易产生查询未命中的情况。但这种操作的消耗很大,如果数据需要经过复杂运算再写入缓存。频繁的更行会影响服务器的性能,且内存都去更新缓存了就没有内存来读取数据了。
删除缓存优势是,效率高,无论更新的操作复杂与否,都是直接删除缓存中的数据。但删除缓存的下一次查询可能会未命中所以可能会查询数据库。
对比起来还是删除缓存的优势比较大。固定下来是删除缓存,就要选择是先更新数据库还是先删除缓存了。
如果先删除缓存再更新数据库,如果第二步失败则可能导致数据库和缓存获得相同的错误数据。
如果先更新数据库再删除缓存,如果第二步失败则会出现数据库和缓存的数据不一致的情况,这种情况采用重试的解决。

所以选择先更新数据库再删除缓存的策略。

redis的单线程架构

redis的网络io和键值对读写是单线程的。其他功能如持久化、异步删除等是依赖其他线程来执行的。其底层不是单线程的。1、对服务端程序来说,线程切换和锁通常很影响性能,redis采用单线程就避免了这两个问题,降低了消耗。2、redis的大部分操作是在内存上完成的,所以性能高。3、redis采用了io多路复用机制,使其能够处理并发请求。

抽象类和接口的区别:
虽然抽象类可以有构造方法,但是和接口一样都不能实例化。抽象类的构造方法只是在子类实例化的时候自动初始化父类。
接口不能有非抽象方法,抽象类可以有非抽象方法。
接口不能有普通成员变量只能有静态变量,抽象类可以有普通成员变量。

static可以修饰什么对象
java类中包含了成员变量、方法、构造器、初始化块和内部类五种成员。static除了不能修饰构造器之外其他皆可。

进程的状态:
创建、就绪、运行、阻塞、销毁。

线程的状态:
初始、可运行、阻塞、等待、超时等待、终止。

Bean的生命周期:

bean定义—bean初始化—生存期—销毁

1、spring启动查找并加载需要被Spring管理的bean进行bean的实例化。

2、bean实例化后对bean的引入和值注入到bean的属性中

3、如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法

4、 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入

5、 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。

6、如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。

7、 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用

8、 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。

9、此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。

10、如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

synchronized和lock的区别

synchronized是一个关键字,lock是一个接口。

synchronized可以作用在代码块和方法上,lock只能作用在代码里。

synchronized在代码执行完或者出现异常的时候会自动释放锁,lock不会自动释放,需要在finally中手动释放。

synchronized可以作用在静态方法,普通方法和代码块。在静态方法时锁住的是类对象,在普通方法时锁住的是实例对象。

synchronized无法知道是否已经获取了锁,如果没拿到就会一直等待。lock可以通过trylock判断是否加锁成功,还可以设置超时时间。

Redis的高可用

主从复制:一主多从,主节点负责读写,从节点只负责读。分担主节点读数据的压力。主节点执行完写数据的任务会把数据同步到从节点。

哨兵模式:分布式架构,包含多个哨兵节点和数据节点。每个哨兵节点都监视着数据节点和其他哨兵节点,当发现节点不可达时会对节点做出下线标识。如果被标记的是主节点,它就会与其他哨兵节点协商,避免误判。当大多数哨兵节点认为主节点不可达时,它们便会选择出一个哨兵节点来做自动故障转移工作,可以将从节点晋升为主节点,同时还会实时的通知到应用方。整个过程自动,实现高可用。

JVM的运行时数据区

1、程序计数器:一块很小的内存区域[内存中唯一不会发生内存溢出的区域]

​ 作用:通过改变这个计数器的值来选取下一条需要执行的字节码指令。

​ java虚拟机的多线程是通过线程流切换并分配处理器执行时间的方式实现的,任何一个时刻一个处理器只会执行一个线程中的指令,为了线程切换后能恢复正确的执行位置,需要独立的程序计数器来代表程序执行到哪儿了。

​ 特点:1、每个线程都有独立的程序计数器

​ 2、线程私有,每个线程互不影响

​ 3、唯一不会oom的区域[oom是内存不足时杀死一个使用大量内存的非系统进程]

​ 4、如果线程执行的是一个Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行的是Native方法,那么计数器值为空(Undefined)。

2、java虚拟机栈:线程私有

​ 作用:每一个方法从调用直至完成的过程。对应这个栈帧在虚拟机栈进栈出栈的过程

​ 存放8种基本数据类型的变量

​ 虚拟机栈可能出现的两种异常:

​ 1、StackOverflowError栈溢出:线程请求的占深度>虚拟机所允许的深度。

​ 2、OutOfMemoryError:虚拟机栈在扩展时无法申请到足够的内存。

​ 特点:1、线程私有

​ 2、保存各种基本数据类型

​ 3、内存空间在编译期间完成分配

3、本地方法栈:本地方法栈与虚拟机栈基本一样,只不过本地方法栈是为java原生Native方法服务,虚拟机栈是为普通java方法服务。

4、java堆

​ 作用:存放对象实例,几乎所有的对象实例都在这里分配内存

​ 特点:1、虚拟机管理内存中最大的一块

​ 2、线程共享

​ 3、在虚拟机启动的时候创建

​ 4、GC管理的主要区域,因此也成为GC堆

​ 5、Java堆允许处于物理上不连续的内存空间中,只要逻辑上连续即可。

5、方法区

​ 作用:用于存储已经被虚拟机加载的类信息、常量、静态变量、即编译器编码后的代码等数据

​ 特点:存常量和静态变量并且是线程共享。

6、运行时常量池

​ 作用(这两个作用其实就是类加载过程当中的某个步骤):

​ 1、保存Class文件中描述的符号引用。

​ 2、把翻译出来的直接引用存储在运行时常量中。

​ 特点:

​ 即不要求常量一定只有在编译期才能产生,而在运行期间也可能将新的常量放入到池中,如String类的intern()方法。

​ 受到方法区内存的限制,可能会OOM。

创建对象的过程

1、检查这个指令的参数是否能在常量池中定位到一个类的符号引用。

2、检查这个符号引用代表的类是否已经被加载、解析、初始化过。

3、若没有则先执行相应的类加载过程

4、类加载后为对象分配内存

分配内存的方式

1:堆内存连续:指针碰撞

所有用过的内存放在一侧,空闲的放在另一侧,中间以指针分割。指针碰撞就是让指针移动对象大小相等的距离。

2:堆内存不连续:空闲列表

虚拟机维护一个列表,记录可用的内存块,分配时找一块足够的内存空间给对象实例并更新列表记录

GC怎么判断一个对象是否需要被回收

引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时就给计数器+1,引用失效时给技术器-1。任何时候计数器为0的对象就不能再使用了。

优点:实现简单,垃圾对象便于辨识

​ 判定效率高,回收没有延迟

缺点:1、需要单独的字段存储计数器,增加了存储空间的开销

​ 2、每次赋值都需要更新计数器,伴随加法和减法操作,增加了时间开销。

​ 3、无法处理循环引用的问题,这是一条致命的缺陷,导致在Java回收的垃圾回收器中没有使用这类算法。

可达性分析法

java主要通过可达性分析法来判断对象是否已死。

算法思路:1、通过一系列称为”GC Root“的对象作为起始点。

​ 2、从这些节点开始向下搜索,搜索走过的路径称为引用链

​ 3、当一个对象到GC Root没有任何引用链条相连(即GC Root到这个对象不可达),说明这个对象是不可用的。

引用

无论是通过引用计数法还是可达性分析法来判断对象的引用是否可达,判断对象是否存活都与“引用”有关。而引用包括很多种:(强度从大到小排序)

强引用:通过new关键字出来的引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

软引用: 通过new SoftReference()创建对象,在系统要发生内存溢出之前会将这些对象列入回收范围内进行第二次回收。

弱引用:被弱引用关联的对象只能生存到下次GC发生之前,GC作用的时候无论当前内存是否足够,都会回收到只被弱引用关联的对象。

虚引用:虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就就是能在这个对象被GC回收的时候得到一个系统通知。

垃圾收集算法

1、标记清除算法

​ 分为两步:1、标记阶段,标记处所有需要回收的对象

​ 2、清除阶段,统一回收所有被标记的对象

​ 算法不足:1、效率问题,标记和清除两个阶段效率都不高。

​ 2、空间问题,标记清楚之后会产生大量不连续的内存碎片。内存碎片多导致出现需要占用较大内存的对象时,因无连续内存而提前GC。

2、复制算法

​ 流程:1、将可用内存划分为大小相等的两块,每次只使用其中一块。

​ 2、当这一块内存用完了,就将还活着的对象复制到另一块上,再将曾经使用的那一块空间清理掉。

​ 优势:1、每次都对半个内存区进行回收,内存分配时不需要考虑内存碎片。

​ 2、实现简单,运行高效。

​ 劣势:可用内存为实际内存的一半,内存开销大。

3、标记整理法

​ 流程:1、标记,对要存活的对象进行标记。

​ 2、整理,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

垃圾收集算法的选用:

​ 根据存活周期不同将内存划分为几块,一般分为新生代和老年代

​ 新生代:每次GC都会有大批对象死去,只有少量存活。采用复制算法。

​ 老年代:对象存活率高、没有额外空间进行分配担保。采用标记清除或标记整理算法进行回收。

HotSpot算法实现

枚举根节点:GC使用可达性分析法,所以需要遍历所有GC Roots,而GC Roots的节点主要在全局引用(常量或静态变量)与执行上下文(栈帧中的本地变量表)中。当程序越大方法区就很大,且对象不断创建。只能在能确保一致性的快照状态进行枚举。

需要解决的两个问题:

​ 1、GC在分析过程中,对象引用关系在不断变化的情况,分析结果的准确性和一致性无法得到保证。

​ 2、如果要逐个检查来寻找可用的GC Roots,会消耗掉很多时间。

解决方法:

​ 解决问题1:GC进行时,必须停顿所有java执行进程(stop the world),也可以说枚举根节点的时候必须停顿。

​ 解决问题2:使用oopMap来实现快速定位GC Roots的枚举。

当执行系统停顿下来后,不需要一个不漏的去检查完所有上下文和全局的引用位置,因为虚拟机是有办法直接得知哪些地方存放着对象引用

HotSpot实现中通过一组成为OopMap的数据结构来达到这个目的:

1、在类加载完成的时候,HotSpot就把对象中对应偏移量的对应类型数据计算出来

2、在JIT编译的时候,就会在特定的位置记录下栈和寄存器中那些位置是引用的。

3、这样在GC扫描的时候就可以直接得知这些信息。

安全点(在此位置进行GC不会出现准确性和一致性问题)

使用OooMap可以协助HotSpot快速准确的完成根节点的枚举,但是会出现以下的问题。

一、如果为每一条指令都生成对应的Oopmap,那么会需要大量额外空间,这样GC的空间成本会很高。

解决方法:HotSpot利用安全点来解决,即程序执行的时候,并非所有地方都能挺下来GC,只有到安全点的时候才暂停。

安全点的选定不能太少——>导致GC等待时间过长。
安全点的选定不能太多——>导致过于频繁的GC以增加运行时的负荷。
因此,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定。
例如:方法调用、循环跳转、指令序列复用等代码,具有这些功能的指令才会产生Safepoint。

二、如何在GC发生的时候,让所有线程都跑到最近的安全点上再停顿下来。

方案一:抢先式中断(几乎没有虚拟机采用该方案):GC发生的时候,首先把所有线程全部中断,如果发现有线程中断的地方不再安全点上,就恢复该线程,让他跑到安全点上。
方案二:主动式中断:GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行的时候主动去轮询这个标志,发现中断标志为true的时候就自己中断挂起。(轮询标志的地方和安全点的地方是重合的)
安全区域(线程运行到安全区域内,标识自己进入了safe region,那么在gc时就会忽略这个进程)

解决了什么问题:安全点虽然保证了程序执行时在不太长时间就会遇到可GC的Safepoint,但程序不执行的时候,例如处于sleep或blocked状态,那这个时候线程无法响应JVM的中断请求。

原理:

1、当线程执行到安全区域中的代码时,首先标识自己已经进入了Safe Region, 这样,在这段时间里JVM发起GC时,就不用管标识了自己为Safe Region状态的线程了。
2、线程离开Safe Region时,他要检查系统是否已经完成了根节点枚举,如果完成了,线程继续执行,否则他就必须等待直到收到可以安全离开Safe Region的信号为止。

垃圾收集器

1、串行垃圾回收器:

为单线程环境设计只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务器。

堆内存中新生代垃圾回收器(Serial)

优势:古老,稳定,简单高效,只使用一个线程去回收,对限定单个cpu的环境,没有线程相互的开销,可以获得最高的单线程垃圾收集效率。是jvm在client模式下默认的新生代收集器。

劣势:在垃圾收集的过程中需要暂停所有其他工作线程。

堆内存中老年代垃圾回收器(Serial old)

Serial垃圾收集器的老年代版本,同样是单线程垃圾收集器,使用标记整理法,同样是运行在client的默认老年代垃圾收集器,在老年代中,也充当cms收集器的后背垃圾收集方案。

2、并行垃圾收集器(ParNew)

ParNew收集器是Serial收集器的多线程版本。可以启动多个线程同时进行GC(其余行为和Serial收集器完全一样)

ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,并且除了Serial收集器外,只有他能与CMS收集器配合工作。

新生代使用复制算法老年代使用标记整理法

3、Parallel Scavenge收集器

Parallel Scavenge收集器为一个新生代收集器,使用复制算法并且是并行的多线程收集器。

I/O流

流的选用:

字节流(input/outputStream)字符流(reader/writer):

字节流:非文本文件,例如图片、视频等。因为其内部存储都是二进制数据。

字符流:文本文件。

字符输出流特点:

1、输出操作,对应的文件不存在的时候会自动创建

多线程

作用:

1、发挥多核cpu的优势

一个线程的多段逻辑在多核cpu上同时工作,提升cpu的使用效率

2、防止阻塞

单核多线程相对于单核单线程的优势就是防止阻塞,因为单核单线程当当前线程的逻辑没有处理结束,整个cpu就阻塞了。但是多线程对cpu实现分片,在多个线程之间切换,就算一个线程阻塞也不会影响其他线程的进度。

sleep()和wait()有什么区别

sleep不会释放锁,wait会释放锁并进入等待状态。

sleep不需要被唤醒,休眠时候开始阻塞,线程监控依然保持着,休眠结束自动运行。wait需要被重新唤醒。

如何保证线程依次执行

使用join方法

自旋锁

**解释:**当多个线程对一个数据进行操作的时候, 每一个线程先读取数据的值n并为其加1,然后往回写,在写的过程中会调用CAS(compare and swap)比较当前值是否为刚查询的值n,如果是则说明在操作过程中没有线程对其操作,修改为n+1即可。如果不是n,则将现在读取的值置为n并重复之前的操作。

ABA问题:

针对自旋锁读取的值为n,当写的时候虽然还是n,但是可能是多个线程操作完成之后又重新自旋一次到n了。(就是原本是0,有很多线程操作之后认为该操作结束了又重新赋值为0了)

解决方法:

加版本(version/boolean)

自旋时间过长的问题解决:

可以制定CAS一共循环多少次,超过这个次数直接失败/挂起线程。

保障CAS操作的原子性问题

排它锁:

只有一个线程能访问代码

共享锁:

可以允许有多个线程访问代码

读写锁:

-读锁,不允许写,但允许同时读

-写锁,写的时候,既不允许读也不允许写

统一锁:

线程一锁定A资源等待B资源,线程二锁定B资源等待A资源就会造成死锁。这时候将锁的范围扩大为A+B就可以避免这个问题。

分段锁:

对于一个列表锁定某一段,其他段还可以继续其他操作。代表:ConcurrentHashMap

线程池的理解和运用:

一次创建多个线程,重复使用,避免每次创建。

如何理解线程池的7个参数

1、核心线程数:线程池创建多少个线程。(要达到一般的并发量)

2、最大线程数:超过核心线程数的是临时线程。

3、生存时间:

4、时间单位 秒:

5、任务队列:内存用有界且合理的长度(不能将任务队列设为无界队列,要是并发请求多的时候可能会消耗完内存)

6、线程工厂:值为线程工厂名。工厂名字一定要有意义。

7、拒绝策略:jdk中的四个(丢弃,抛异常,交于提交任务的线程,将存活时间最长的任务丢弃并创建新的任务)一般不用自带的,一般将请求放置于mq并且记录日志。

进程/线程

进程是分配资源的最小单位:一个软件在未启动时存在于磁盘空间,当启动后内存会为其分配进程空间,这是分配资源的最小单位。

线程是程序运行的基本单元:当一个程序开始运行的时候,会找到主线程开始运行(java中是main函数,一个线程一个线程开始运行)

java中线程的状态

new->runnable->waiting->timed_witing->terminated

新建 运行 等待 时间等待 结束

**等待:**自动唤醒

**时间等待:**手动唤醒

java中结束线程的方式

1、stop()方法直接结束。(不用)

2、使用共享变量,while(flag)终止时候将flag置为false

3、interrupt()方法,使用此方法时,线程内部的终端标志位会置为true,此线程终止。

wait()和sleep()的区别

sleep属于Tread类中的static方法,wait属于Object类的方法

sleep属于TIMED_WATITING,将自动被唤醒、wait属于WAITING ,需要手动唤醒。

sleep方法在持有锁时,执行,不会释放锁资源、wait在执行后,会释放锁资源

sleep可以在持有锁或不持有锁时,执行。wait方法必须持有锁才能执行。

wait方法会将持有锁的线程从owner添加到waitset集合中,这个操作是在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitoer对象的

并发编程的三大特性

1、原子性(加锁)

2、可见性(使用volatile关键字,使对当前属性的读写不允许使用cpu缓存,直接与内存交互)

3、有序性(volatile )

java中的四种引用类型

强、软、弱、虚

**强引用:**始终处于可达状态,不会被gc回收。所以强引用是导致java内存泄露的主要原因之一

**软引用:**当系统内存足够时不会被回收,当内存空间不足时会被回收。软引用通常用在内存敏感的程序中,作为缓存使用

**弱引用:**只要gc开始运行,不管内存时候够用都会被回收,可以解决内存泄露的问题,TreadLocal就是基于弱引用解决内存泄露的问题。

**虚引用:**不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

锁的分类

可重入锁、不可重入锁

**重入:**当前获取到A锁,在获取之后尝试再次获取A锁可以直接拿到。

**不可重入:**当前线程获取到A锁,在获取之后尝试再次获取A锁,无法获取到,因为A锁被当前线程占用着,需要等待自己释放锁再获取锁。

乐观锁、悲观锁

**乐观锁:**获取不到锁,可以让cpu调度,重新尝试获取锁

**悲观锁:**获取不到锁,会将当前线程挂起,挂起或涉及到用户态和内核态切换,比较消耗资源

用户态:JVM可以执行的指令,不需要借助操作系统执行

内核态:jvm不可以自行执行,需要操作系统才可执行。

公平锁、不公平锁

**公平锁:**线程A获取到了锁资源,线程B排队,线程C排到B的后面等B拿到锁资源或者取消后才可以去竞争锁资源。

**不公平锁:**线程A获取到了锁资源,线程B排队,线程C再来,A释放锁后立刻与B竞争。

互斥锁、共享锁

**互斥锁:**一个线程拿到资源,其他线程就不能在访问

**共享锁:**多个线程共享资源。

JDK1.6对synchronized的优化

锁消除:

在synchronized修饰的代码中,如果不存在操作临界资源的情况,就会消除锁,即使写了synchronized也不会触发。

锁膨胀:

如果在一个循环中,频繁获取和释放锁资源,消耗很大。锁膨胀就是将锁的范围扩大,避免频繁的竞争和获取锁资源带来的不必要的消耗。

*无锁:*当前对象没有作为锁存在。

*偏向锁:*如果当前的锁资源,只有一个线程在频繁的获取和释放,那么只需要判断当前指向的线程是否是当前线程。如果是,直接加锁。如果不是,基于CAS的方式将偏向锁指向当前线程。还是获取不到就触发锁升级为轻量级锁。(偏向锁状态出现了锁竞争的情况)

*轻量级锁:*会采用自旋锁的方式去频繁的以CAS的形式获取锁资源(采用的是自适应自旋锁)。如果获取到,直接上锁。如果自旋了一定次数,没拿到锁资源,锁升级。

*重量级锁:*最传统的synchronized方式,拿不到锁资源,就挂起当前线程。(用户态&内核态)

AQS是什么

**用处:**一种维护一个请求队列的锁实现。

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

**实现:**AbstractQueueSynchronizer抽象类,是juc包下的一个基类,juc下的很多内容都是基于AQS实现了部分功能。如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch等。

首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型state变量。(类似于Semaphore会将剩余可用资源数赋值给state )

其次AQS中维护了一个双向链表,有head,tail且每个节点都是node对象

ReentrantLock和synchronized的区别

核心区别

ReentrantLock是一个类,synchronized是一个关键字,都是在jvm层面实现互斥锁的方式。

效率区别

如果竞争比较激烈,推荐ReentrantLock实现,不存在锁升级概念。

而synchronized是存在锁升级概念的,如果升级到重量级锁,是不存在锁降级的。

底层实现区别

ReentrantLock是基于AQS实现的。

synchronized是基于ObjectMonitor实现的

ReentrantReadWriteLock读写锁

为什么要读写锁

写需要同步,读需要能够异步。单纯的使用互斥锁会降低读的效率,单纯的使用共享锁在写的时候会出现同步问题。

实现原理

还是基于AQS,对state进行操作,如果没有拿到锁资源就去AQS排队。

读锁操作:对state的高16位进行操作。

写锁操作:对state的低16位进行操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuye0Qmg-1681861898296)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230411093019524.png)]

写锁的饥饿问题:读锁是共享锁,假如一直有线程在读,写线程就无法获取资源。

解决方案:读锁在排队时不但需要查看state的高16位,还需要看state的低16位,如果写锁也在排队,就得等写锁释放资源了才能获取读锁资源。

JDK自带的线程池

1、newFixedTreadPool

public static ExecutorService newFIxedThreadPOol(int nThread){//最大线程个数
    return new ThreadPoolExecutor(nThreads,
 nThreads,
 0L,
TimeUnit.Milliseconds),
    new LinkedBlockingQueue<Runnable>());//如果创建的线程超过最大线程个数,就会将新创建的线程放置到阻塞队列中。等工作线程处理结束后再处理阻塞队列中的线程。
}

2、newSingleThreadExecutor

单例线程池,线程池中只有一个工作线程在处理任务

涉及顺序消费的时候可以使用此线程。

public static ExecutorService newSingleThreadExecutor(){
    return new FinalizableDelegatedExecutorService(
    new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>()))
}

static class FinalizableDelegatedExecutorService extends DelegatedExecutorService{
    FinalizableDelegatedExecutorService(ExecutorService executor){
        super(executor);
    }
    protected void finalize(){
        super.shutdown();
    }
}

3、newCachedThreadPool

public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,//无核心工作线程 
Integer.MAX_VALUE,60L
,TimeUnit,SECONDS,new SunchronousQueue<Runnable>())
}

无核心工作线程,在工作线程创建之后会直接放置到阻塞队列里。因为无工作线程,在放置到阻塞队列中之后会自动创建一个非核心线程用以处理阻塞队列中的任务。

4、newScheduleTreadPool

这个线程池可以以一个周期去执行一个任务,或者是延迟多久执行一个任务一次。

public static ScheduledExecutorService newScheduleThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize); 
}

5、newWorkStealingPool

基于Forkjoin:其他线程池都是所有挂起的线程都存在一个阻塞队列,而newWorkStealingPool是每个线程都有自己的阻塞队列,且当线程自己的阻塞队列中没有任务执行时就会去其他线程的阻塞队列中窃取任务帮其执行以提升执行效率。

线程池的核心参数有什么

在使用JDK提供的方式创建线程池的时候,只有两个参数

public ThreadPoolExecutor(
	int corePoolSize,//核心工作线程(核心工作线程在任务执行之后也不会被销毁)	
	int maximumPoolSize,//最大工作线程(代表当前线程池中一共可以有多少个工作线程)
	long keepAliveTime,//非核心工作线程在阻塞队列位置等待的时间(非核心线程在阻塞队列中没有任务后多少时间结束本线程 )
    TimeUnit unit,//非核心线程在阻塞队列中等待时间的单位
    BlockQueue<Runnable> workQueue,//任务在没有核心线程处理时,任务先放置到阻塞队列中
    ThreadFactoty threadFactory,//构建线程的线程工厂,可以设置Tread的一些信息
    RejectedExecutionHandler handler,//当前线程池无法处理投递过来的任务时,执行当前的拒绝策略
){
    
}

线程池的状态

running:正常状态,正常接收任务,工作线程正常处理阻塞队列的任务。

shutdown:不会接收新的任务,正常处理的任务正常进行,阻塞队列的任务也会做完。

stop:不会接收新任务,正在处理任务的线程被中断,阻塞队列的任务不处理。

tidying:过渡状态,等待shutdown的线程处理结束所有任务/等待stop的线程关闭工作线程之后就进入这个状态。进入这个状态的线程就可以调用terminated()方法了

terminated:线程池关闭。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sqrOVxi2-1681861898297)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230412091555780.png)]

线程池的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bPWjm8tf-1681861898298)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230412094928532.png)]

execute()的源码【通过这可以看线程池的执行流程】
public void execute(Runnable command) {
    
    if (command == null)
        throw new NullPointerException();

    /** ctl记录着workCount和runState */
    int c = ctl.get();

    /** case1:如果线程池中的线程数量小于核心线程数,那么创建线程并执行 */
    if (workerCountOf(c) < corePoolSize) {  // workerCountof(c):获取当前活动线程数
        /**
         * 在线程池中新建一个新的线程
         * command:需要执行的Runnable线程
         * true:新增线程时,【当前活动的线程数】是否 < corePoolSize
         * false:新增线程时,【当前活动的线程数】是否 < maximumPoolSize
         */
        if (addWorker(command, true))
            return;
        // 添加新线程失败,则重新获取【当前活动的线程数】
        c = ctl.get();
    }

    /** 第二步:如果当前线程池是运行状态 且 任务添加到队列成功(即,case2:如果workCount >= corePoolSize) */
    if (isRunning(c) && workQueue.offer(command)) { // 添加command到workQueue队列中
        // 重新获取ctl
        int recheck = ctl.get();
        // 再次check一下,当前线程池是否是运行状态,如果不是运行时状态,则把刚刚放入到workQueue队列中的command移除掉
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0){  // 如果【当前活动的线程数】为0,则执行addWorker方法
            /**
                     * null:只创建线程,但不去启动
                     * false:添加线程时,根据maximumPoolSize来判断
                     *
                     * 如果workerCountOf(recheck) > 0,则直接返回,在队列中的command稍后会出队列并且执行
                     */
            addWorker(null, false);
        }
        /**
         * 第三步:满足以下两种条件之一,进入第三步判断语句
         *  case1:线程池不是正在运行状态,即:isRunning(c)==false
         *  case2:workCount>=corePoolSize 并且添加workQueue队列失败。即:workQueue.offer(command)== false
         *  由于第二个参数传的是false,所以如果workCount < maximumPoolSize,则创建执行线程;否则进入方法体执行reject(command)
         *  如果是true的话则和核心线程数进行比较
         */
    }
    else if (!addWorker(command, false))
        reject(command);    // 执行线程创建失败的拒绝策略
}

一个对象多少个字节

Object o=new Object();
//对象的字节数必须是8的倍数,如果不足8会补足。
//markword8字节,class pointer 4字节最低12字节,但由于不是8的倍数会补足为16字节
private static class o(){
    int a;
    int b;
}
//o对象至少24字节
//一个markword和class pointer合起来最少12字节,但是包含两个int,一个int4字节,所以合起来20字节,不足8的倍数,补足。即24字节。

数据库调优

一、打开慢查询日志,查看慢查询语句

1、设置数据库

show variables like '%quer%';# 显示全局设置中的关于quer的
set global slow_query_log = on; # 打开慢查询  立即设置立即生效
set global long_query_time = 1; # 设置慢查询时间 单位 s (秒)  
#重启mysql客户端

# 如果想让 配置立即生效linux 可以在 myf,windows是my.ini 配置文件,对这些值进行设置,一旦设置是永久# 保存的。如果我们仅在这里做set global 当我们重启数据库服务的时候,他就会还原成之前的状态,也就是说 
# slow_query_log 是 OFF 它就是 OFF

2、在控制台中查看慢查询日志

sudo vim /usr/local/mysql/data/baidudeMacBook-Pro-slow.log

参数:type

从上到下性能逐渐变差,至少要到range级别。

二、根据实际修改sql

不要用select *:

1、会查出很多不需要的字段,增加sql执行时间,增加网络开销。

2、失去MySql优化器覆盖索引策略优化的可能性。

将子查询换成inner join
select stu_name,(select address_name from stu_address ad where s.address_id=ad.address_id) from student s where s.stu_id="001"; 

select s.stu_name,ad.address_name from student as s where address_id inner join (select address_name from stu_address where s.stu_id ="001")as ad on s.address_id= ad.address_id;
字段格式要统一
# 当user_id是varchar的时候
select id,user_id from account_tbl where user_id='1001';#会走索引
select id,user_id from account_tbl where user_id=1001;不会走索引

牛客刷题

一、
public class Test{ 
    private static int i=1;
    public int getNext(){ 
         return i++;
    } 
    public static void main(String [] args){ 
        Test test=new Test(); 
        Test testObject=new Test(); 
        test.getNext(); 
        testObject.getNext(); 
        System.out.println(testObject.getNext()); 
    } 
}

解析:着重点是static关键字,静态变量i的值适用整个程序。所以所有调用都会对i赋值。但是i++是先return再加加,所以sout中的i++是在输出之后++的,所以结果是3。

二、
下面有关java final的基本规则,描述错误的是?
A.final修饰的类不能被继承
B.final修饰的成员变量只允许赋值一次,且只能在类方法赋值
C.final修饰的局部变量即为常量,只能赋值一次。
D.final修饰的方法不允许被子类覆盖

解析:final修饰的成员变量是基本数据类型时,赋值后无法改变。

final修饰的成员变量是引用数据类型时,指向地址不能改变,但值可以改变。

成员变量的赋值方式:

1、声明时直接赋值

2、构造器中赋值

3、初始代码块中赋值

更多推荐

java开发工程师面试题

本文发布于:2023-04-30 08:31:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/c8f6ea9f3ad132254d0f4e365e33e793.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:面试题   工程师   java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!

  • 113620文章数
  • 28833阅读数
  • 0评论数