文章目录
1 JMM
1.1 计算机硬件存储体系
1.2 为什么需要JMM
1.3 JMM定义
1.4 JMM特性
1.4.1 可见性
1.4.2 原子性
1.4.3 有序性
1.5 happens-before
1.5.1 8条规则
(1)次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作 。
(2)锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
(3)volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上的先后)对这个变量的读操作。
(4)传递规则:如果操作A先行发生于B,B先行发生于C,那么A先行发生于C。
(5)线程启动规则:Tread对象的start方法先行发生于此线程的每一个动作。
(6)线程中断规则:
(7)线程终止规则:
(8)对象终结规则:
2 volatile
2.0 特点
可见性。
有序性。
不保证原子性。
2.1 内存语义
(1)写变量时,会把线程中的变量副本刷回主存。
(2)通知其他线程,副本作废,重新回主存中去重新读取变量
(3)总结:就是写的时候,写到内存中。读的时候直接从内存中读取。
2.2 内存屏障
2.2.1 什么是内存屏障
2.2.2 分类
2.2.2 volatile变量规则
2.2.3 可见性case解析
2.2.3.1 案例
package org.example.volatiletest;
import java.util.concurrent.TimeUnit;
/**
* 如果flag没有用volatile修饰,线程一一直在执行,出不来的。
* 用了的话,就可以出来。
*/
public class Test {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(String.format("----------- thread %s come in--------", name));
while (flag) {
}
System.out.println(String.format("----------- thread %s come out--------", name));
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println(String.format("----------- main thread will come out, flag = %s--------", flag));
}
}
2.2.3.2 解析
2.2.4 原子性case解析
2.2.4.1 案例
package org.example.volatiletest;
import java.util.concurrent.TimeUnit;
/**
* synchronized修饰会得到稳定的10000的结果。
* volatile就保证不了
*/
class MyNumber {
private volatile int number;
public void addPlusPlus() {
number++;
}
public int getNumber() {
return number;
}
}
public class AtomicTest {
public static void main(String[] args) {
MyNumber myNumber = new MyNumber();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
myNumber.addPlusPlus();
}
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myNumber.getNumber());
}
}
2.2.4.2 解析
2.2.5 有序性case解析
2.2.5.1 案例
2.2.5.2 解析
2.2.6 volatile的使用场景
(1)单一赋值可以,但是包含复合操作的不可以(i++等操作)。
(2)状态标志, boolean变量的读写。
(3)开销较低的读写锁策略。
(4)两重校验锁的单例模式
(5)与AtomicIntegerFieldUpdater等配合使用,实现对对象属性的原子性的修改。
线程范围内共享数据
package com.sk.thread;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
public static void main(String[] args) {
for(int i=0;i<2;i++){
new Thread(new Runnable(){
@Override
public void run() {
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+ " has put data :" + data);
threadData.put(Thread.currentThread(), data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
int data = threadData.get(Thread.currentThread());
System.out.println("A from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
static class B{
public void get(){
int data = threadData.get(Thread.currentThread());
System.out.println("B from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
}
有时候会抛异常,没搞懂
Thread-0 has put data :-1772161753
Thread-1 has put data :-566086542
A from Thread-0 get data :-1772161753
Exception in thread "Thread-1" B from Thread-0 get data :-1772161753
java.lang.NullPointerException
at com.sk.thread.ThreadScopeShareData$A.get(ThreadScopeShareData.java:29)
at com.sk.thread.ThreadScopeShareData$1.run(ThreadScopeShareData.java:20)
at java.lang.Thread.run(Thread.java:745)