最后
分享一些系统的面试题,大家可以拿去刷一刷,准备面试涨薪。
这些面试题相对应的技术点:
- JVM
- MySQL
- Mybatis
- MongoDB
- Redis
- Spring
- Spring boot
- Spring cloud
- Kafka
- RabbitMQ
- Nginx
- …
大类就是:
- Java基础
- 数据结构与算法
- 并发编程
- 数据库
- 设计模式
- 微服务
- 消息中间件
-
- 什么是伪共享
-
如何避免伪共享
-
最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。 可以的话请给我一个三连支持一下我哟,我们下期再见
物理计算机中的并发问题和Java虚拟机有很多相似之处。
为了解决处理器与内存之间的速度矛盾,引入了高速缓存
。
高速缓存的引入带来了问题:缓存一致性
。多路处理器系统中,每个处理器有各自的高速缓存,而他们又共享同一主内存。当多个处理器的运算任务额都涉及同一块主存区域的时候,将可能导致各自的缓存数据不一致。
为了解决一致性的问题,需要各个处理器在访问缓存时都遵循一些协议,在读写时要根据协议来进行操作。我们将会多次提到“内存模型”一词,可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问时的过程抽象
。不同的物理机器可以拥有不同的内存模型。而Java虚拟机也拥有自己的内存模型
。
除了增加高速缓存,处理器会对代码进行乱序执行优化
。对应JVM中的指令重排序
。
《Java虚拟机规范》中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
此前(如:C语言)直接使用物理硬件和操作系统的内存模型,导致一套程序在不同的平台上出现不同的错误。
主内存与工作内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
这里的变量不包括局部变量和参数,因为其实线程私有的,不会被共享,自然不会存在竞争问题。
Java内存模型规定了所有的变量都存储在主内存中
,每条线程还有自己的工作内存(可与高速缓存类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取赋值)都必须在工作内存中进行
,而不能直接读写主内存中的变量(包括volatile变量也是这样)。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
内存间交互操作
关于主内存和工作内存之间具体的交互协议:即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节。Java内存模型中定义了以下8种操作来完成,虚拟机实现时必须保证下面提及的每一种操作都是原子的。
-
lock(主内存)
-
unlock(主内存)
-
read(主内存)
-
load(工作内存)
-
use(工作内存)
-
assign(工作内存)
-
store(工作内存)
-
write(主内存)
注意:Java内存模型只要求这两个操作(read、load 操作,store、write 操作)必须按顺序执行,而没有保证是连续执行。
也就是说read
和write
之间是可以插入其他指令的,如
read a、read b、load b、load a
Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的
原子性
一个操作要么都发生,要么都不发生
-
基本数据的读写都是具备原子性的(32位机器要注意long和double)
-
synchronized块之间的操作也是具备原子性的
可见性
一个线程修改了值,其他线程能够立刻知道
-
和普通变量一样,volatile 变量也是通过主内存作为传递媒介的,但volatile和主内存之间的读和写是立刻发生的
-
除了volatile,synchronized(unlock之前就会同步到主内存中),final(只要没有 this 逃逸)
有序性
在本线程中观察,所有操作都是有序的(指本线程内表现为串行的语义)
在另一个线程中观察,所有操作都是无序的(指“指令重排序”现象和“工作内存和主内存同步延迟”现象)
-
volatile本身就包含指令重排序的语义
-
synchronized 则一个变量在同一时刻只允许一条线程对其进行 lock 操作
Java内存模型中有一个先行发生原则,它是判断数据是否存在竞争,线程是否安全的重要手段。
我们举个例子来说明说明什么是先行发生原则
// A 线程
i = 1;
// B 线程
j = i;
// C 线程
j = 2;
如果 A 先行发生于 B,且 C 没有登场,那么,j 的值一定是 1。如果 C 登场了,仍旧只是 A 先行发生于 B,那么 j 可能是 2,也可能是 1
Java 内存模型中有一些天然的先行发生原则,其中介绍下面两条;
-
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。 这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
-
volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
volatile变量规则
对于volatile变量的读和写而言,如果在实际执行时间上有写在读前的话
(如线程A的assign在先于线程B的use执行,前面说了assign、use是原子操作)
,那么就有写在读前的先行发生关系,这样就保证了一切对于volatile变量写操作可见的变量(即happens before volatile写操作的其他变量操作所造成的一切影响),对于后面的volatile变量读操作也是可见的。如果换成可普通变量,即使是有时间上的写在读前,但如不是同一线程就没有happens关系,这样就不能保证可见性。
private int value = 0;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
-
假设有线程 A 和 B ,在时间顺序上,A 先调用 setValue(2),B 再调用 getValue(),那么,B 得到的值还是不确定的。
-
可以把 value 修饰为 volatile 类型,由于 setter 方法对 value 的修改不依赖于原值,所以将会是线程安全的。
Java中的线程安全排序:
-
不可变
-
绝对线程安全
-
相对线程安全
-
线程兼容
-
线程对立
this逃逸
在构造器构造还未彻底完成前(即实例初始化阶段还未完成),将自身this引用向外抛出并被其他线程复制(访问)了该引用。
class ThisEscape {
int i;
static ThisEscape obj;
public ThisEscape() { // 由于指令重排序,所以不能确定这两部谁先进行
i = 1;
obj = this;
}
}
// 如果线程A还没来得及为i赋值,线程B就使用了这个obj.i;会导致空指针。或者其他情况下会导致对象不完整。
什么情况下会This逃逸?
(1)如上述的明显将this抛出
(2)在构造器中内部类使用外部类情况:内部类访问外部类是没有任何条件的,也不要任何代价,也就造成了当外部类还未初始化完成的时候,内部类就尝试获取为初始化完成的变量
-
在构造器中启动线程:启动的线程任务是内部类,在内部类中xxx.this访问了外部类实例,就会发生访问到还未初始化完成的变量
-
在构造器中注册事件,这是因为在构造器中监听事件是有回调函数(可能访问了操作了实例变量),而事件监听一般都是异步的。在还未初始化完成之前就可能发生回调访问了未初始化的变量。
不可变
-
final
修饰:只要一个对象被正确地构建出来(即没有发生this
引用逃逸) -
如果多线程共享的是一个基本数据类型,那只要再定义时使用
final
关键字修饰就可以保证它是不可变的。如果是一个对象,那就需要保证它自己不可变(如:String
无论用什么方法都不会影响它原来的值,它是由final
修饰的)
绝对线程安全
再Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。
如:Vector类,它的add()、get()等方法都是由synchronized修饰的,但不意味着调用它的时候不需要原子操作
(指要么都做要么都不做)了。
public void method(Vector vec) {
synchronized(vec) {
vec.set(…);
vec.get(…);
}
}
相对线程安全
指我们通常意义上的线程安全,它需要保证这个对象单次的操作是线程安全的。如:Vector类。
线程兼容
指我们通常所说的线程不安全。指对象本身并不安全,但可以通过调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。
线程对立
指不管怎样操作都无法在多线程环境中并发使用。由于Java本身就具备支持多线程地特性,线程对立地情况很少出现。
互斥同步
同步是指在多线程并发访问共享数据时,保证共享数据同一时刻只被一条线程使用(或者是一些,当使用信号量时)。
互斥是同步的一种手段:临界区、互斥量和信号量都是常见的互斥实现方法。
互斥同步这四个字里:互斥是因,同步是果。互斥是方法,同步是目的。
我们拿Reentrant
比synchronized
多了一些高级功能:
- 我们可以认为synchronized是Reentrant的一个子集,但是经过JDK6优化后,他们性能差不多,而synchronized使用方便
总结
以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!
下面给大家分享下我的面试大全资料
- 第一份是我的后端JAVA面试大全
后端JAVA面试大全
- 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理
MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
- 第三份是Spring全家桶资料
MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
[外链图片转存中…(img-UELx1Nj6-1715001357563)]
后端JAVA面试大全
- 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理
[外链图片转存中…(img-qDvEW6vo-1715001357563)]
MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
- 第三份是Spring全家桶资料
[外链图片转存中…(img-rSSziIq8-1715001357564)]
MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理