JMM三大核心
重排序
-
- JVM重排
- CPU指令重排序
- 内存重排序:实际是可见性问题
可见性
- voliate来保证可见性和重排序 voliate不适用于a++,不保证原子性
-
- 两个作用:
-
-
- 可见性。读一个voliate变量之前,需要先相应的本地缓存失效,必须到主存读取最新值,写一个voliate时会立即刷入到主内存
- 禁止指令重排序优化:解决单例双重锁乱序问题
-
voliate只能保证它之前的操作都被后面看到
适用场景:
- 多个线程共享,其中一个线程改变了该值,其他线程可以立即得到这个值
- 是无锁的 不能替代synchronized
- 只能用于属性,不具备原子性
- 提供了可见性
- 提供了Happes-before保证
- 对long和double赋值是原子的
原子性
什么是原子性?
一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的
long和double是每个32位写一次。在64位上是原子的
JMM应用实例
单例模式的8种写法,单例和并发的关系
为什么需要单例?
- 一个对象很耗资源,而且不变动,没必要每次都新建,节省内存和计算
- 保证结果正确
- 方便管理
适用场景:
- 无状态的工具类:比如日志工具类
- 全局信息类
单例模式
/**
* 饿汉式
*/
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public Singleton1 getInstance() {
return INSTANCE;
}
}
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public Singleton2 getInstance() {
return INSTANCE;
}
}
private static Singleton3 INSTANCE = null;
/**
* 懒汉式 (线程不安全)
**/
private Singleton3() {
}
public Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
推荐用
private static Singleton4 INSTANCE = null;
private Singleton4() {
}
/**
* 加synchronized版本
* @return
*/
public synchronized Singleton4 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
return INSTANCE;
}
优点:线程安全,延迟加载,效率较高
为什么要DCL:
- 线程安全
- 单check行不行?
- 性能问题?
- 为什么需要voliate?
-
- 新建对象实际上有三个步骤:
-
-
- 分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
-
(2)(3)会被处理器优化,发生重排序
这时有可能其他线程判断INSTANCE == null会判断不是null,但是实际使用的时候INSTANCE.属性可能是null
这里要思考一个问题?
为什么synchroized可以保证可见性,重排序,原子性,那为什么 INSTANCE = new Singleton5();还是有重排序的问题?还需要加voliate
sync能保证如下代码,按;分割的语句禁止重排序,但是比如INSTANCE = new Singleton5();这一个语句多个指令。sync无法禁止这种重排序。但是voliate是可以的,所以需要加上voliate
/**
* 双重检查 推荐使用
*
* @return
*/
public Singleton5 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Singleton5.class) {
int a = 2;
int b = 3;
int c = 3;
if (INSTANCE == null) {
INSTANCE = new Singleton5();
}
}
}
return INSTANCE;
}
/**
* 静态内部类
*/
private Singleton6() {
}
private static class SingletonInstance {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonInstance.INSTANCE;
}
public enum Singleton8 {
/**
* 枚举单例
*/
INSTANCE;
}
饿饿汉:简单,但是没有lazy loading
懒汉:有线程安全问题
静态内部类:可用
双重检查:面试用
枚举:实际使用最佳
用哪种单例的实现方案最好?
枚举最好。
写法简单,线程安全有保障,枚举是特殊类,枚举会被编译长final Class,实际是静态对象,同样也是懒加载,避免反序列化破坏单例
如果一开始要加载很多资源,应该使用懒加载
饿汉式如果是对象的创建需要配置文件就不适用
关于可见性的问题
for(;;)和while是有区别的
int a = 1;
int b = 2;
void change() {
a = 3;
b = a;
}
// 可以尝试在b=a后面加c=b 会发现c的值是不固定的
void print() {
System.out.println("b=" + b + ",a=" + a);
}
public static void main(String[] args) throws InterruptedException {
// 如果使用while 是有可能出现b=3,a=1的
for (; ; ) {
ThreadVisi threadVisi = new ThreadVisi();
new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadVisi.change();
}).start();
new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadVisi.print();
}).start();
}
}
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ; ) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch count = new CountDownLatch(1);
Thread thread = new Thread(() -> {
try {
count.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a = 1;
x = b;
});
Thread thread1 = new Thread(() -> {
try {
count.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
b = 1;
y = a;
});
thread1.start();
thread.start();
count.countDown();
thread1.join();
thread.join();
String result = "第" + i + "次";
if (x == 0 && y == 0) {
System.out.println(result + "x=" + x + ",y=" + y);
break;
}
System.out.println(result + "x=" + x + ",y=" + y);
}
}
重排序的好处:
- 提高响应速度
JMM三大核心
重排序
-
- JVM重排
- CPU指令重排序
- 内存重排序:实际是可见性问题
可见性
- voliate来保证可见性和重排序 voliate不适用于a++,不保证原子性
-
- 两个作用:
-
-
- 可见性。读一个voliate变量之前,需要先相应的本地缓存失效,必须到主存读取最新值,写一个voliate时会立即刷入到主内存
- 禁止指令重排序优化:解决单例双重锁乱序问题
-
voliate只能保证它之前的操作都被后面看到
适用场景:
- 多个线程共享,其中一个线程改变了该值,其他线程可以立即得到这个值
- 是无锁的 不能替代synchronized
- 只能用于属性,不具备原子性
- 提供了可见性
- 提供了Happes-before保证
- 对long和double赋值是原子的
原子性
什么是原子性?
一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的
long和double是每个32位写一次。在64位上是原子的
JMM应用实例
单例模式的8种写法,单例和并发的关系
为什么需要单例?
- 一个对象很耗资源,而且不变动,没必要每次都新建,节省内存和计算
- 保证结果正确
- 方便管理
适用场景:
- 无状态的工具类:比如日志工具类
- 全局信息类
单例模式
/**
* 饿汉式
*/
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public Singleton1 getInstance() {
return INSTANCE;
}
}
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public Singleton2 getInstance() {
return INSTANCE;
}
}
private static Singleton3 INSTANCE = null;
/**
* 懒汉式 (线程不安全)
**/
private Singleton3() {
}
public Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
推荐用
private static Singleton4 INSTANCE = null;
private Singleton4() {
}
/**
* 加synchronized版本
* @return
*/
public synchronized Singleton4 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
return INSTANCE;
}
优点:线程安全,延迟加载,效率较高
为什么要DCL:
- 线程安全
- 单check行不行?
- 性能问题?
- 为什么需要voliate?
-
- 新建对象实际上有三个步骤:
-
-
- 分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
-
(2)(3)会被处理器优化,发生重排序
这时有可能其他线程判断INSTANCE == null会判断不是null,但是实际使用的时候INSTANCE.属性可能是null
这里要思考一个问题?
为什么synchroized可以保证可见性,重排序,原子性,那为什么 INSTANCE = new Singleton5();还是有重排序的问题?还需要加voliate
sync能保证如下代码,按;分割的语句禁止重排序,但是比如INSTANCE = new Singleton5();这一个语句多个指令。sync无法禁止这种重排序。但是voliate是可以的,所以需要加上voliate
/**
* 双重检查 推荐使用
*
* @return
*/
public Singleton5 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Singleton5.class) {
int a = 2;
int b = 3;
int c = 3;
if (INSTANCE == null) {
INSTANCE = new Singleton5();
}
}
}
return INSTANCE;
}
/**
* 静态内部类
*/
private Singleton6() {
}
private static class SingletonInstance {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonInstance.INSTANCE;
}
public enum Singleton8 {
/**
* 枚举单例
*/
INSTANCE;
}
饿饿汉:简单,但是没有lazy loading
懒汉:有线程安全问题
静态内部类:可用
双重检查:面试用
枚举:实际使用最佳
用哪种单例的实现方案最好?
枚举最好。
写法简单,线程安全有保障,枚举是特殊类,枚举会被编译长final Class,实际是静态对象,同样也是懒加载,避免反序列化破坏单例
如果一开始要加载很多资源,应该使用懒加载
饿汉式如果是对象的创建需要配置文件就不适用
关于可见性的问题
for(;;)和while是有区别的
int a = 1;
int b = 2;
void change() {
a = 3;
b = a;
}
// 可以尝试在b=a后面加c=b 会发现c的值是不固定的
void print() {
System.out.println("b=" + b + ",a=" + a);
}
public static void main(String[] args) throws InterruptedException {
// 如果使用while 是有可能出现b=3,a=1的
for (; ; ) {
ThreadVisi threadVisi = new ThreadVisi();
new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadVisi.change();
}).start();
new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadVisi.print();
}).start();
}
}
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ; ) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch count = new CountDownLatch(1);
Thread thread = new Thread(() -> {
try {
count.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a = 1;
x = b;
});
Thread thread1 = new Thread(() -> {
try {
count.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
b = 1;
y = a;
});
thread1.start();
thread.start();
count.countDown();
thread1.join();
thread.join();
String result = "第" + i + "次";
if (x == 0 && y == 0) {
System.out.println(result + "x=" + x + ",y=" + y);
break;
}
System.out.println(result + "x=" + x + ",y=" + y);
}
}
重排序的好处:
- 提高响应速度
AQS是JUC提供的一个抽象类,用于实现同步的逻辑
关于CountDownLatch,先看下以下代码
private static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("减1了");
countDownLatch.countDown();
}).start();
new Thread(() -> {
System.out.println("减1了");
countDownLatch.countDown();
}).start();
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("睡了3秒也减1了");
countDownLatch.countDown();
}).start();
new Thread(() -> {
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("多线程被唤醒了1");
}).start();
new Thread(() -> {
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("多线程被唤醒了2");
}).start();
countDownLatch.await(10, TimeUnit.SECONDS);
System.out.println("多线程被唤醒了3");
}
也就是可以多个线程等待多个线程释放。
那么思想应该是使用了countDown()的去释放锁,调用了wait的去排队等待,如果state被改为0,那么就会被唤醒
共享锁的tryAcquireShared(int acquires)
返回的是一个整型值:
- 如果该值小于0,则代表当前线程获取共享锁失败
- 如果该值大于0,则代表当前线程获取共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功
- 如果该值等于0,则代表当前线程获取共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败
共享锁的获取:
public final void acquireShared(int arg) {
// 返回-1 说明没有其他线程获取共享锁的行为会失败 所以可以执行以下了
// 这里试想以下 当前线程是wait 那么已知state是-1了 获取共享锁失败了
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 如果当前state的状态已经成0了,说明都countdown了 那么应该返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
先看固定代码:
private void doAcquireShared(int arg) {
// 循环入队
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点的前一个节点
final Node p = node.predecessor();
// 如果前一个节点是头节点
if (p == head) {
// 获取当前state
int r = tryAcquireShared(arg);
// state>=0 说明state还不到0 还有线程没有countdown
if (r >= 0) {
// 设置头节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 如果前一个节点不是头节点 那么看是否应该park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
释放锁
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// 如果返回true 才执行下面的 返回false 直接返回false
// 返回true就代表state为0
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
CountDownlatch的尝试释放共享锁,把cas和自旋的完美结合。直观体现
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
staet为0代表什么,本来就等了5个线程执行,现在5个线程执行完了,那么该唤醒等待的线程了。
锁的释放
private void doReleaseShared() {
for (;;) {
Node h = head;
// 如果头节点不为空 并且头节点不等于尾节点
if (h != null && h != tail) {
// 获取当前头节点的状态
int ws = h.waitStatus;
// 如果当前节点状态是SIGNAL
if (ws == Node.SIGNAL) {
// 更改当前等待状态
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 当前节点状态是0并且尝试更改状态为-3
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}