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 进程号
找到死锁问题本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句