一、Switch数据类型支持哪些?
byte short char int Byte Short Char Integer String Enum
二、Java有哪些锁?区别在哪?底层如何实现的?为什么非公平锁效率高?
1.公平锁/非公平锁
Java ReentrantLock,通过构造函数指定该锁是否是公平锁, Synchronized 非公平锁
2.可重入锁
Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
3.独享锁/共享锁
对于Java ReentrantLock和Synchronized而言,其是独享锁。ReadWriteLock,其读锁是共享锁,其写锁是独享锁
4.互斥锁/读写锁
ReentrantLock / ReadWriteLock
5.乐观锁/悲观锁
不加锁的并发操作一定会出问题的认知就是悲观锁;乐观的认为,不加锁的并发操作是没有事情的。
6.分段锁
ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
7.偏向锁/轻量级锁/重量级锁
**偏向锁:**是一种针对加锁操作的优化手段。在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。JVM启用了偏向锁模式:jdk6之后默认开启新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。
**轻量级锁:**倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。轻量级锁在降级的时候直接变为无锁状态!(查看之前在同步代码块外调用hashCode()方法)
**重量级锁:**轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。重量级锁是基于 Monitor 机制,并且在 Monitor 中记录 hashCode。
8.自旋锁
**自旋锁:**当一个线程尝试去获取某一把锁的时候,如果这个锁已经被另外一个线程占有了,那么此线程就无法获取这把锁,该线程会等待,间隔一段时间后再次尝试获取。这种采用循环加锁,等待锁释放的机制就称为自旋锁(spinlock)
三、Java内存模型
在运行时数据区里,会根据用途进行划分:
1.Java虚拟机栈(栈区)
2.本地方法栈
3.Java堆(堆区)
4.方法区
5.程序计数器
Java堆
Java虚拟机栈
本地方法栈
十分类似Java虚拟机栈,与Java虚拟机区别在于:服务对象,即Java虚拟机栈为执行 Java 方法服务;本地方法栈为执行 Native方法服务
方法区
注:其内部包含一个运行时常量池,具体介绍如下:
程序计数器
四、线程池使用场景及其核心参数说明
使用场景
线程池的使用场景可以根据并发量和任务执行时长两个维度来划分:
高并发、任务执行时间短:
线程池中的线程数可以设置得较少,以减少线程上下文的切换。
这种场景下,线程池的主要作用是异步处理任务,避免主线程阻塞,从而提高响应速度。
高并发、任务执行时间长:
解决这种类型任务的关键在于整体架构的设计,而不是仅仅依赖线程池。
可能需要在业务逻辑中分析并使用中间件(如消息队列MQ)对任务进行拆分和解耦。
在这种情况下,可以将可预见的会发生阻塞操作的代码块放入线程池中执行,以异步非阻塞的方式快速响应。
并发不高、任务执行时间短:
线程池中的线程数可以设置得较少,以减少线程上下文的切换。
并发不高、任务执行时间长:
需要区分任务类型为IO密集型任务和CPU密集型任务。
对于IO密集型任务,由于IO操作不占用CPU,可以加大线程池中的线程数目,让CPU处理更多的业务。
对于CPU密集型任务,线程池线程数可以设置为少一些(以CPU核数+1为准),以减少线程上下文的切换。
核心参数
1.CorePoolSize 服务器核数
2.MaxPoolSize 建议:根据IO密集还是CPU密集;cpu密集设置服务器核数,减少线程切换时间,IO,两倍服务器核数
3.QueueCapacity 队列大小:根据业务需求设置大小
4.KeepAliveSeconds 默认 60s,根据服务器能力,可以适当加长为120s
5.ThreadNamePrefix 线程名前缀
6.WaitForTasksToCompleteOnShutdown 等待所有任务结束后再关闭线程池
7.RejectedExecutionHandler 拒绝策略
拒绝策略
1.默认AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
2.DiscardPolicy 丢弃任务不抛异常
3.DiscardOldestPolicy 丢弃队列最前任务放最新任务
4.CallerRunsPolicy 主线程处理当前任务
5.自定义策略DefaultRejectedHandler 队列当前长度超过80%,阻塞主线程
五、Threadlocal原理和使用场景
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
六、实现多线程通讯
使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想。大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
使用 Object 类的 wait()/notify()
Object 类提供了线程间通信的方法:wait()、notify()、notifyAll(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
使用JUC工具类 CountDownLatch
java
public class TestSync {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
countDownLatch.countDown();
}
});
//线程B
Thread threadB = new Thread(() -> {
while (true) {
if (list.size() != 5) {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
break;
}
});
//需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再启动线程A
threadA.start();
}
}
使用 ReentrantLock 结合 Condition
java
public class TestSync {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
lock.lock();
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
condition.signal();
}
lock.unlock();
});
//线程B
Thread threadB = new Thread(() -> {
lock.lock();
if (list.size() != 5) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
lock.unlock();
});
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.start();
}
}
这种方式使用起来并不是很好,代码编写复杂,而且线程 B 在被 A 唤醒之后由于没有获取锁还是不能立即执行,也就是说,A 在唤醒操作之后,并不释放锁。这种方法跟 Object 的 wait()/notify()
一样。
基本 LockSupport 实现线程间的阻塞和唤醒
LockSupport
是一种非常灵活的实现线程间阻塞和唤醒的工具,使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。
java
public class TestSync {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//线程B
final Thread threadB = new Thread(() -> {
if (list.size() != 5) {
LockSupport.park();
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
LockSupport.unpark(threadB);
}
});
threadA.start();
threadB.start();
}
}
七、Mybatis初始化和执行原理
1.创建SqlSessionFactoryBuilder对象,调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
2.由SqlSessionFactory创建SqlSession 对象,没有手动设置的话事务默认开启
3.调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回。
1、读取MyBatis配置文件
mybatis-config.xml为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,例如数据库连接信息。
2、加载映射文件(SQL映射文件,一般是XXXMapper.xml)
该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。XXXMapper.xml可以在mybatis-config.xml文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3、构造会话工厂
通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory。
4、创建会话对象
由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
5、Executor执行器
MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
6、MappedStatement对象
在 Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
7、输入参数映射
输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
8、输出结果映射
输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
八、Spring mvc初始化和执行原理
Spring MVC是基于Servlet的技术,它提供了核心类DispatcherServlet和相关的组件,围绕这个核心类有一个完整的流程
(1)浏览器提交请求经web容器(比如tomcat)转发到中央调度器dispatcherServlet。
(2)中央调度器调用处理器映射器handerMapping,处理器映射器根据请求的url找到处理该请求对应的处理器hander及相关的拦截器intercepter,将它们封装成一个处理器执行链并返回给中央调度器
(3)中央调度器根据处理器执行链中的处理器找到对应的处理器适配器handerAdaptor
(4)处理适配器调用处理器执行对应的方法并将返回的结果封装为一个对象modelAndView中返回给中央处理器,当然在处理器执行方法前如果方法有拦截器的话会先依次执行拦截器的prehander方法,方法执行结束后会依次执行拦截器的posthander方法。
(5)中央调度器获取到modelAndView对象后,调用视图解析器viewResolver,将modelAndView封装为视图对象
(6)中央调度器获取到视图对象后,进行渲染,生成最后的响应返回给浏览器。
九、Springboot如何自定义starter
1、引入项目的配置依赖
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
2、创建xxxService类,完成相关的操作逻辑
java
public class StringService {
private String str1;
private String str2;
private String default_str;
public String getStr1() {
return str1;
}
public void setStr1(String str1) {
this.str1 = str1;
}
public String getStr2() {
return str2;
}
public void setStr2(String str2) {
this.str2 = str2;
}
public String getDefault_str() {
return default_str;
}
public void setDefault_str(String default_str) {
this.default_str = default_str;
}
public String addStr(){
if(str1 != null){
if(str2 != null){
return str1 + "," + str2;
}
return str1;
}
return default_str;
}
}
3、 定义xxxProperties类,属性配置类,完成属性配置相关的操作,比如设置属性前缀,用于在application.properties中配置
java
//指定项目在属性文件中配置的前缀为str,即可以在属性文件中通过 str.str1=springboot,就可以改变属性类字段 str1 的值了
@SuppressWarnings("StringProperties")
@ConfigurationProperties(prefix = "str")
public class StringProperties {
public static final String DEFAULT_STR1 = "I know, you need me";
public static final String DEFAULT_STR2 = "but I also need you";
private String str1 = DEFAULT_STR1;
private String str2 = DEFAULT_STR2;
public String getStr1() {
return str1;
}
public void setStr1(String str1) {
this.str1 = str1;
}
public String getStr2() {
return str2;
}
public void setStr2(String str2) {
this.str2 = str2;
}
}
4、定义xxxAutoConfiguration类,自动配置类,用于完成Bean创建等工作
java
// 定义 java 配置类
@Configuration
//引入StringService
@ConditionalOnClass({StringService.class})
// 将 application.properties 的相关的属性字段与该类一一对应,并生成 Bean
@EnableConfigurationProperties(StringProperties.class)
public class StringAutoConfiguration {
// 注入属性类
@Autowired
private StringProperties stringProperties;
@Bean
// 当容器没有这个 Bean 的时候才创建这个 Bean
@ConditionalOnMissingBean(StringService.class)
public StringService helloworldService() {
StringService stringService = new StringService();
stringService.setStr1(stringProperties.getStr1());
stringService.setStr2(stringProperties.getStr2());
return stringService;
}
}
5、在resources下创建目录META-INF,在 META-INF 目录下创建 spring.factories,在SpringBoot启动时会根据此文件来加载项目的自动化配置类
java
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lhf.springboot.config.StringAutoConfiguration
验证:其他项目中使用自定义的Starter
- 在新项目中引入自定义Starter依赖配置
java
<!--引入自定义Starter-->
<dependency>
<groupId>com.lhf.springboot</groupId>
<artifactId>spring-boot-starter-string</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 编写一个简单的Controller
java
@RestController
public class StringController {
@Autowired
private StringService stringService; //引入自定义Starter中的StringService
@RequestMapping("/")
public String addString(){
return stringService.addStr();
}
}
- 编写属性配置文件,内容如下:
java
#配置自定义的属性信息
str.str1=为什么我的眼里常含泪水
str.str2=那是因为我对你爱的深沉
- 启动项目进行访问,效果如图: