JMM
Volatile
深入理解CAS
Unsafe类
各种锁的理解
面试、工作排查问题
JMM
- Java内存模型,不存在的东西,概念,约定。
关于JMM同步的约定
- 线程解锁前,必须把共享变量立即刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
8种操作
Volatile
Volatile是Java虚拟机提供轻量级的同步机制
- 不保证可见性
- 不保证原子性
- 禁止指令重排
不保证可见性
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//不加volatile程序就会死循环
//加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(()->{ //线程一对主内存的变化不知道
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
原子性:不可分割
- 线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
如果不加lock和 synchronized 无法保证原子性
使用原子类解决原子性
- 这些类的底层都直接和操作系统挂钩,在内存中修改值,Unsafe类十一额特殊的存在
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
//volatile 不保证原子性
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement(); //加1操作 CAS
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
if (Thread.activeCount() > 2) { //main gc
Thread.yield();
}
System.out.println(num);
}
}
指令重排
- 程序员写的程序并不是按照写的那样取执行
- 处理在进行指令重排的时候,会考虑数据的依赖性关系
- volatile可以避免指令重排
内存屏障、CPU指令作用:
- 保证特定的操作执行顺序
- 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
- 源代码->编译器的优化重排->指令并行也可能会重排->内存系统一定会重排->执行
彻底玩转单例模式
深入理解CAS
- CAS:比较当前内存中的值和主内存中的值,如果这个值是期望的,则执行操作,如果不是则一直循环
- 缺点:因为底层实自旋锁,循环会耗时
- 一次只能保证一个共享变量的原子性
- ABA问题
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望更新
//如果期望值正确则更新,否则不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));;
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));;
System.out.println(atomicInteger.get());
}
}
ABA问题(狸猫换太子)
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo02 {
//CAS 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望更新
//如果期望值正确则更新,否则不更新
// ======== 捣乱的线程= =======
System.out.println(atomicInteger.compareAndSet(2020, 2021));;
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));;
System.out.println(atomicInteger.get());
//========= 期望的的线程 =========
System.out.println(atomicInteger.compareAndSet(2020, 2022));;
System.out.println(atomicInteger.get());
}
}
- 泛型是包装类,注意对象引用问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CASDemo03 {
public static void main(String[] args) {
//注意如果泛型是包装类,注意对象的引用问题
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);
new Thread(()->{
int stamp = atomicInteger.getStamp(); //获取当前版本号
System.out.println("a1=" + stamp);
//加延迟保证版本号是同一个
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(1, 2,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
System.out.println("a2=" + atomicInteger.getStamp());
atomicInteger.compareAndSet(2, 1,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
System.out.println("a3=" + atomicInteger.getStamp());
}, "a").start();
new Thread(()->{
int stamp = atomicInteger.getStamp(); //获取当前版本号
System.out.println("b1=" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(1, 6, stamp, stamp + 1);
System.out.println("b1=" + atomicInteger.getStamp());
}, "b").start();
}
}
Unsafe类
各种锁的理解
公平锁
- 非常公平,不能够插队,必须先来后到
非公平锁
- 非常不公平,可以插队(默认都是非公平的)
public ReentrantLock() {
this.sync = new ReentrantLock.NonfairSync();
}
public ReentrantLock(boolean fair) {
this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
}
可重入锁
可重入锁synchronized版本
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
}, "a").start();
new Thread(()->{
phone.call();
}, "b").start();
}
}
class Phone{
public synchronized void sms() {
System.out.println(Thread.currentThread().getName() + "sms");
//这里也有锁
call();
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "call");
}
}
可重入锁Lock版本
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
}, "a").start();
new Thread(()->{
phone.call();
}, "b").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms() {
lock.lock(); //细节问题,锁必须配对
try {
System.out.println(Thread.currentThread().getName() + "sms");
//这里也有锁
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "==> myLock");
//自旋锁
while (!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void unLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "==> myUnLock");
atomicReference.compareAndSet(thread, null);
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
//ReentrantLock lock = new ReentrantLock();
//底层使用的CAS自旋锁
SpinLockDemo lock = new SpinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}, "t2").start();
}
}
死锁
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA, lockB), "t1").start();
new Thread(new MyThread(lockA, lockB), "t2").start();
}
}
class MyThread implements Runnable {
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "lock:" + lockA + "->get" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "lock:" + lockB+ "->get" + lockA);
}
}
}
}
死锁的排查
- 使用 jps -1 定位进程号
- 使用 jstack 进程号 找到死锁问题
面试,工作种排查问题
- 日志
- 堆栈