JMM : Java内存模型,不存在的东西,概念!约定! 关于JMM的一些同步的约定: 1. 线程解锁前,必须把共享变量立刻刷回主存。 2. 线程加锁前,必须读取主存中的新值到工作内存中! 3. 加锁和解锁是同一把锁
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)
内存交互操作 内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
JMM对这八种指令的使用,制定了如下规则:
Volatile
特点:
原子类
依旧是JUC包下的类
测试:
COPYpublic class VDemo02 {
// volatile 不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; // 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = ; i <= ; i++) {
new Thread(()->{
for (int j = ; j < ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
禁止指令重排
使用内存屏障。CPU指令。作用: 1. 保证特定的操作的执行顺序! 2. 可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生
饿汉式(可能浪费资源)
COPYpublic class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[*];
private byte[] data2 = new byte[*];
private byte[] data3 = new byte[*];
private byte[] data4 = new byte[*];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL 懒汉式
COPYpublic class LazyMan {
private static boolean hcode = false;
private LazyMan(){
synchronized (LazyMan.class){
if (hcode == false){
hcode= true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
// 反射破坏~
public static void main(String[] args) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Field hcode= LazyMan.class.getDeclaredField("hcode");
qinjiang.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
hcode.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
枚举实现懒汉式(安全)
COPY// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环! 缺点: 1. 循环会耗时 2. 一次性只能保证一个共享变量的原子性 3. ABA问题 (线程操作后又将标志位改回去了)
ABA问题 解决方法
原子引用:使用乐观锁的思想,给CAS加上版本号
COPYpublic class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
// 正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(,);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
Lock lock = new ReentrantLock(true);
atomicStampedReference.compareAndSet(, ,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + );
System.out.println("a2=>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(, ,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + ));
System.out.println("a3=>"+atomicStampedReference.getStamp());
},"a").start();
// 乐观锁的原理相同!
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(, ,
stamp, stamp + ));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实 例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;
ReentrantLock()
的添加参数,true为公平锁,默认false为非公平锁只要拿到类方法的锁,方法里面引用其它方法的锁也就能自动获取到了。
就是while死循环等待版本号标志成立才执行。CAS的底层就是这样的。
例子:
COPYpublic class SpinlockDemo {
// int 0
// Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> mylock");
// 自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
// 解锁
// 加锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
死锁就是双方都想获取对方持有的锁,导致程序停滞。
如何解决死锁
jps -l
定位进程号。jstack 进程号
找到死锁问题