8000 完善部分知识 · coderli7/toBeTopJavaer@b5ba21c · GitHub
[go: up one dir, main page]

Skip to content

Commit b5ba21c

Browse files
author
hollis.zhl
committed
完善部分知识
1 parent d20b488 commit b5ba21c

14 files changed

+1026
-14
lines changed

docs/_sidebar.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,27 +77,27 @@
7777

7878
* [switch对String的支持](/basics/java-basic/switch-string.md)
7979

80-
* 字符串池
80+
* [字符串池](/basics/java-basic/string-pool.md)
8181

82-
* 常量池(运行时常量池、[Class常量池](/basics/java-basic/class-contant-pool.md)
82+
* 常量池([运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)[Class常量池](/basics/java-basic/class-contant-pool.md)
8383

84-
* intern
84+
* [intern](/basics/java-basic/intern.md)
8585

8686
* Java中各种关键字
8787

88-
* transient
88+
* [transient](basics/java-basic/transient-in-java.md)
8989

90-
* instanceof
90+
* [instanceof](basics/java-basic/instanceof-in-java.md)
9191

92-
* volatile
92+
* [volatile](basics/concurrent-coding/volatile.md)
9393

94-
* synchronized
94+
* [synchronized](basics/concurrent-coding/synchronized.md)
9595

96-
* final
96+
* [final](basics/java-basic/final-in-java.md)
9797

98-
* static
98+
* [static](basics/java-basic/static-in-java.md)
9999

100-
* const
100+
* [const](basics/java-basic/const-in-java.md)
101101

102102
* 集合类
103103

@@ -107,7 +107,9 @@
107107

108108
* [Set和List区别?](/basics/java-basic/set-vs-list.md)
109109

110-
* [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)
110+
* [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)
111+
112+
* [ArrayList使用了transient关键字进行存储优化,而Vector没有,为什么?](/basics/java-basic/why-transient-in-arraylist.md)
111113

112114
* [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)
113115

@@ -219,15 +221,17 @@
219221

220222
* [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md)
221223

222-
* [transient](basics/java-basic/transient.md)
224+
* [transient](basics/java-basic/transient-in-java.md)
223225

224226
* [序列化底层原理](basics/java-basic/serialize-principle.md)
225227

226228
* [序列化如何破坏单例模式](basics/java-basic/serialize-singleto 6D47 n.md)
227229

228230
* [protobuf](basics/java-basic/protobuf.md)
229231

230-
* 为什么说序列化并不安全
232+
* [Apache-Commons-Collections的反序列化漏洞](basics/java-basic/bug-in-apache-commons-collections.md)
233+
234+
* [fastjson的反序列化漏洞](basics/java-basic/bug-in-fastjson.md)
231235

232236
* 注解
233237

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如`synchronized``volatile``final``concurren包`等。在[前一篇][2]文章中,我们也介绍了`synchronized`的用法及原理。本文,来分析一下另外一个关键字——`volatile`
2+
3+
本文就围绕`volatile`展开,主要介绍`volatile`的用法、`volatile`的原理,以及`volatile`是如何提供可见性和有序性保障的等。
4+
5+
`volatile`这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中,都有`volatile`关键字。都可以用来声明变量或者对象。下面简单来介绍一下Java语言中的`volatile`关键字。
6+
7+
### volatile的用法
8+
9+
`volatile`通常被比喻成"轻量级的`synchronized`",也是Java并发编程中比较重要的一个关键字。和`synchronized`不同,`volatile`是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
10+
11+
`volatile`的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用`volatile`修饰就可以了。
12+
13+
public class Singleton {
14+
private volatile static Singleton singleton;
15+
private Singleton (){}
16+
public static Singleton getSingleton() {
17+
if (singleton == null) {
18+
synchronized (Singleton.class) {
19+
if (singleton == null) {
20+
singleton = new Singleton();
21+
}
22+
}
23+
}
24+
return singleton;
25+
}
26+
}
27+
28+
29+
如以上代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用`volatile`关键字修饰可能被多个线程同时访问到的singleton。
30+
31+
### volatile的原理
32+
33+
[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中我们曾经介绍过,为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。
34+
35+
但是,对于`volatile`变量,当对`volatile`变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。
36+
37+
但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现`缓存一致性协议`
38+
39+
**缓存一致性协议**:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
40+
41+
所以,如果一个变量被`volatile`所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个`volatile`在并发编程中,其值在多个缓存中是可见的。
42+
43+
### volatile与可见性
44+
45+
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
46+
47+
我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
48+
49+
前面的关于`volatile`的原理中介绍过了,Java中的`volatile`关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用`volatile`来保证多线程操作时变量的可见性。
50+
51+
### volatile与有序性
52+
53+
有序性即程序执行的顺序按照代码的先后顺序执行。
54+
55+
我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如`load->add->save` 有可能被优化成`load->save->add` 。这就是可能存在有序性问题。
56+
57+
`volatile`除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排优化等。
58+
59+
普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。
60+
61+
volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被`volatile`修饰的变量的操作,会严格按照代码顺序执行,`load->add->save` 的执行顺序就是:load、add、save。
62+
63+
### volatile与原子性
64+
65+
原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
66+
67+
我们在[Java的并发编程中的多线程问题到底是怎么回事儿?][3]中分析过:线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
68+
69+
在上一篇文章中,我们介绍`synchronized`的时候,提到过,为了保证原子性,需要通过字节码指令`monitorenter``monitorexit`,但是`volatile`和这两个指令之间是没有任何关系的。
70+
10000
71+
**所以,`volatile`是不能保证原子性的。**
72+
73+
在以下两个场景中可以使用`volatile`来代替`synchronized`
74+
75+
> 1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值。
76+
>
77+
> 2、变量不需要与其他状态变量共同参与不变约束。
78+
79+
除以上场景外,都需要使用其他方式来保证原子性,如`synchronized`或者`concurrent包`
80+
81+
我们来看一下volatile和原子性的例子:
82+
83+
public class Test {
84+
public volatile int inc = 0;
85+
86+
public void increase() {
87+
inc++;
88+
}
89+
90+
public static void main(String[] args) {
91+
final Test test = new Test();
92+
for(int i=0;i<10;i++){
93+
new Thread(){
94+
public void run() {
95+
for(int j=0;j<1000;j++)
96+
test.increase();
97+
};
98+
}.start();
99+
}
100+
101+
while(Thread.activeCount()>1) //保证前面的线程都执行完
102+
Thread.yield();
103+
System.out.println(test.inc);
104+
}
105+
}
106+
107+
108+
以上代码比较简单,就是创建10个线程,然后分别执行1000次`i++`操作。正常情况下,程序的输出结果应该是10000,但是,多次执行的结果都小于10000。这其实就是`volatile`无法满足原子性的原因。
109+
110+
为什么会出现这种情况呢,那就是因为虽然volatile可以保证`inc`在多个线程之间的可见性。但是无法`inc++`的原子性。
111+
112+
### 总结与思考
113+
114+
我们介绍过了`volatile`关键字和`synchronized`关键字。现在我们知道,`synchronized`可以保证原子性、有序性和可见性。而`volatile`却只能保证有序性和可见性。
115+
116+
那么,我们再来看一下双重校验锁实现的单例,已经使用了`synchronized`,为什么还需要`volatile`
117+
118+
public class Singleton {
119+
private volatile static Singleton singleton;
120+
private Singleton (){}
121+
public static Singleton getSingleton() {
122+
if (singleton == null) {
123+
synchronized (Singleton.class) {
124+
if (singleton == null) {
125+
singleton = new Singleton();
126+
}
127+
}
128+
}
129+
return singleton;
130+
}
131+
}
132+
133+
134+
答案,我们在下一篇文章:既生synchronized,何生亮volatile中介绍,敬请关注我的博客(http://47.103.216.138)和公众号(Hollis)。
135+
136+
[1]: http://47.103.216.138/archives/2550
137+
[2]: http://47.103.216.138/archives/2637
138+
[3]: http://47.103.216.138/archives/2618
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
运行时常量池( Runtime Constant Pool)是每一个类或接口的常量池( Constant_Pool)的运行时表示形式。
2+
3+
它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表( SymbolTable)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。
4+
5+
每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。
6+
7+
以上,是Java虚拟机规范中关于运行时常量池的定义。
8+
9+
### 运行时常量池在JDK各个版本中的实现
10+
11+
根据Java虚拟机规范约定:每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口到虚拟机后,就创建对应的运行时常量池。
12+
13+
在不同版本的JDK中,运行时常量池所处的位置也不一样。以HotSpot为例:
14+
15+
在JDK 1.7之前,方法区位于堆内存的永久代中,运行时常量池作为方法区的一部分,也处于永久代中。
16+
17+
因为使用永久代实现方法区可能导致内存泄露问题,所以,从JDK1.7开始,JVM尝试解决这一问题,在1.7中,将原本位于永久代中的运行时常量池移动到堆内存中。(永久代在JDK 1.7并没有完全移除,只是原来方法区中的运行时常量池、类的静态变量等移动到了堆内存中。)
18+
19+
在JDK 1.8中,彻底移除了永久代,方法区通过元空间的方式实现。随之,运行时常量池也在元空间中实现。
20+
21+
### 运行时常量池中常量的来源
22+
23+
运行时常量池中包含了若干种不同的常量:
24+
25+
编译期可知的字面量和符号引用(来自Class常量池)
26+
运行期解析后可获得的常量(如String的intern方法)
27+
28+
所以,运行时常量池中的内容包含:Class常量池中的常量、字符串常量池中的内容
29+
30+
### 运行时常量池、Class常量池、字符串常量池的区别与联系
31+
32+
33+
虚拟机启动过程中,会将各个Class文件中的常量池载入到运行时常量池中。
34+
35+
所以, Class常量池只是一个媒介场所。在JVM真的运行时,需要把常量池中的常量加载到内存中,进入到运行时常量池。
36+
37+
字符串常量池可以理解为运行时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。

0 commit comments

Comments
 (0)
0