private int y = 1;
public void m1() {
a = 1;
x = b;
}
public void m2() {
b = 1;
y = a;
}
public void test() throws Exception {
CyclicBarrier cyc = new CyclicBarrier(2);
Thread thread1 = new Thread(() -> {
try {
cyc.await();
} catch (Throwable e) {
e.printStackTrace();
}
m1();
}, “A”);
Thread thread2 = new Thread(() -> {
try {
cyc.await();
} catch (Throwable e) {
e.printStackTrace();
}
m2();
}, “B”);
thread1.start();
thread2.start();
thread1.join();
thread1.join();
if (x == 0 && y == 0) {
System.out.println(“重排序了。。。。”);
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100000; i++) {
JmmTest jmmTest = new JmmTest();
jmmTest.test();
}
}
}
// 输出结果
重排序了。。。。
重排序了。。。。
重排序了。。。。
重排序了。。。。
final 关键字
=========
对于基本类型前加以 final 修饰,表示被修饰的变量为常数,不可以修改。一个既是static又是final的字段表示只占据一段不能改变的存储空间。
final 用于对象应用时,final 使应用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。
final 关键字如何工作?
对象的最终字段的值在其构造函数中设置。假设对象被“正确”构造,一旦构造了对象,分配给构造函数中的最终字段的值将对所有其他线程可见,无需同步。此外,这些最终字段引用的任何其他对象或数组的可见值将至少与最终字段一样最新。
正确构造对象意味着什么?它只是意味着在构造过程中不允许对正在构造的对象的引用“逃逸”。换句话说,不要将正在构造的对象的引用放在另一个线程可能能够看到它的任何地方;不要将其分配给静态字段,不要将其注册为任何其他对象的侦听器,等等。这些任务应该在构造函数完成后完成,而不是在构造函数中完成。
举个例子:
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x;
int j = f.y;
}
}
}
上面的类是如何使用 final 字段的示例。执行reader的线程一定会看到fx的值 3 ,因为它是最终的。不能保证看到y的值 4 ,因为它不是最终的。如果FinalFieldExample的构造函数如下所示:
public FinalFieldExample() { // bad!
x = 3;
y = 4;
// bad construction - allowing this to escape
global.obj = this;
}
然后该读出的参照线程 此从 global.obj 是不保证看到 x =3。
查看正确构造的字段值的能力很好,但是如果该字段本身是一个引用,那么您还希望您的代码看到它指向的对象(或数组)的最新值。如果您的字段是最终字段,这也是有保证的。因此,您可以拥有指向数组的最终指针,而不必担心其他线程会看到数组引用的正确值,但会看到数组内容的错误值。同样,这里的“正确”是指“截至对象构造函数结束时的最新值”,而不是“可用的最新值”。
现在,说了这么多,如果在一个线程构造了一个不可变对象(即一个只包含 final 字段的对象)之后,你想确保它被所有其他线程正确地看到,你通常仍然需要使用同步。例如,没有其他方法可以确保第二个线程可以看到对不可变对象的引用。程序从 final 字段获得的保证应该通过对如何在代码中管理并发性的深入和仔细的理解来仔细调整。
volatile 关键字
============
volatile 字段是用于在线程之间通信状态的特殊字段。对 volatile 的每次读取都将看到任何线程对该 volatile 的最后一次写入;实际上,程序员将它们指定为字段,因为缓存或重新排序的结果永远不会允许看到“过时”值。禁止编译器和运行时在寄存器中分配它们。它们还必须确保在写入之后,它们会从缓存中刷新到主内存,以便其他线程可以立即看到它们。类似地,在读取易失性字段之前,必须使缓存失效,以便看到的是主存中的值,而不是本地处理器缓存中的值。对易失性变量访问的重新排序也有额外的限制。
在旧的内存模型下,对易失性变量的访问不能相互重新排序,但可以使用非易失性变量访问重新排序。这破坏了易失性字段作为从一个线程向另一个线程发送条件信号的手段的实用性。
在新的内存模型下,易变变量不能相互重新排序仍然是事实。不同之处在于,现在对它们周围的正常字段访问重新排序不再那么容易了。写入易失性字段具有与监视器释放相同的记忆效果,从易失性字段读取具有与监视器获取相同的记忆效果。实际上,由于新的内存模型对易失性字段访问与其他字段访问(无论是否易失性)的重新排序施加了更严格的限制,因此线程A 在写入易失性字段f时可见的任何内容在线程 B 读取f时都变为可见。
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
DCL 单例模式
通过 DCL(double-checked-locking) ,通过延迟初始化单例对象来减少初始化过程中内存的开销,下面是一个双重锁实现单例模式的例子:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/cc7bdf57afcb525f89f0e97a76c93689.jpeg)
难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
[外链图片转存中…(img-71dDdwSt-1713640788274)]
[外链图片转存中…(img-H1SQ20c3-1713640788274)]
[外链图片转存中…(img-hHRiqghn-1713640788275)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!