Volatile 关键字
Volatile 是java虚拟机提供的轻量级同步机制(保证可见性,不保证原子性,禁止指令重排)
可见性之前需要了解
JVM(java虚拟机)
JMM(java内存模型) javamemory model 不真实存在描述的一种规则规范 定义了程序中各个变量(包括实例字段静态字段和构成数组对象的元素)的访问方式
JMM的同步的规定
1 线程解锁前必须把共享变量的值刷新回主内存
2 线程加锁前必须读取主内存的最新值到自己工作内存
3 加解锁是同一把锁
可见性:举个栗子 比如你new了一个student对象 要设置里面的age(默认为15) 的值 new的对象是在主内存中(我们暂时理解主内存是一个大的空间是一种共享区域内存所有线程可以访问),此时有两个线程需要更改student 中age的值 线程一设置的时候会从主内存copy一份student 并且更改age的值为18 更改完成后写到主内存中(另一个线程无法读这个线程里面的值)此时主内存的值被改变此时jmm通知另个线程age已经被更改为18 拎一个线程里面的age也变为了18
不保证原子性: 比如1000线程对一个变量进行++操作 如果是原子性的话会输出1000但是实际值一般不会是1000;(原因是 在一个线程取到值加完后要写的主内存的瞬间被挂起其他线程写入 此时还没有被通知 相当于同样的一个值被覆盖了....自行理解)如何解决 最简单的加上syn关键字 或者用Atomicinteger、AtomicLong .....等一些带原子类型的包装类详情百度
禁止指令重排: 重排的意思是例如x=3 y=4 x++ y++ 这四句可能翻译后的字节码为1324 (4个语句顺序) 也可能为1234 指令重排后的就为1324 假如多线程的话会导致数据不一致
(底层如何实现屏障 通过插入内存屏障禁止在内存屏障天后的指令执行重排优化 写之后加入store 读之后加入load)
单例模式多线程问题(懒汉式线程不安全)
懒汉式多线程获得的不是同一个对象导致不是单例了
单例模式多线程下无效
可以用DCL(双端检锁)但是不一定线程安全,原因是有指令重排的存在加volatile可以禁止指令重排(加入双端检锁也可能发生不安全 因为在你new完的时候 可能切换线程此时实例还是为null 另一个也new 了一个 这样不就两个实例了吗)还有一个重排问题就是可能导致实例为null 需要在instance 前加volatile
安全的代码
public class VolatileMain { private static volatile VolatileMain instance=null;//不加这个volatile可能导致重排对象为null private VolatileMain() { System.out.println(Thread.currentThread().getName()+"构造方法"); } //DCL 双端检测-可以用双端检测加锁前后都进行判断 锁机制 会好很多 public static VolatileMain getInstance() //多个线程就不是单例模式想要单例需要方法加上synchronize关键字 { if (instance==null) { synchronized (VolatileMain.class) { if (instance==null) { instance=new VolatileMain(); } } } return instance; }
CAS知道吗如何实现?(cpu并发原语)
CAS 是判断内存的某个位置是否为预期值如果是这个值就改为新址这个过程是原子性的cas并发原语体现在java语言中就是sun.unsafe 类中的各个方法调用unsafe方法的cas方法这是一种完全依赖于硬件的功能通过它实现了原子操作 cas是系统原语 由若干条指令组层 实现某一功能
CAS比较并交换含义:比如一个线程 需要更改主物理内存的值比如主物理内存age=5; 线程1需要copy一份并修改为指定的值假如是200;此时修改完后需要写进主物理内存,写的时候需要比较原来的期望值是5 ,现在主物理内存如果是5的话就更新不是的话就不更新AutomicInteger.compareAndSet(期望值,要更改的值)
Unsafe类一些代码
比如调用自动增加的方法 getAndIncrement(){ return unsafe.getAndAddInt(本对象,地址,1) }
都会来比较内存地址的值和原来的数值如果一样就更新 有一个dowhile会一直比较
Unsafe类+cas思想(自旋锁--死循环)
CAS缺点:循环时间长 给CPU带来很大开销 引出ABA问题 只能保证共享一个变量
ABA问题
其实这个问题我想到了 提前取出内存中某时刻的数据并在当下时刻比较并替换 这个时间差会导致数据的变化。 假如 一个线程把age初始值为1 两个线程old值为一时才更改 线程一先运行然后立即转到线程2 线程2把age值改成2 然后又马上改成1 此时切换到线程一 线程一认为age 值没有修改 并且把age修改为3
原子更新引用 的概念
(原子引用)AtomicReference<User> <> 里面是泛型 User 就一个get和set 两个 成员变量不贴了
public class AtomicRederenceMy { public static void main(String[] args) { User u=new User(1,"lsi"); User u1=new User(2,"hah "); AtomicReference<User> atomicReference=new AtomicReference<>(); atomicReference.set(u); System.out.println(atomicReference.compareAndSet(u,u1)); System.out.println(atomicReference.compareAndSet(u,u1)); } }
解决办法:
AtomicStampedReference 带时间戳的原子引用
public static void main(String[] args) { Integer myinte=new Integer(100); AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<Integer>(myinte,0); int stamp=atomicStampedReference.getStamp(); new Thread(new Runnable() { @Override public void run() { atomicStampedReference.compareAndSet(myinte,new Integer(10),0,stamp+1); atomicStampedReference.compareAndSet(10,new Integer(100),atomicStampedReference.getStamp()-1,stamp+1); } }).start(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } boolean b=atomicStampedReference.compareAndSet(myinte,new Integer(10),0,stamp+1); System.out.println(atomicStampedReference.getReference()+"---"+b); } }).start(); }
未完待续...
原文地址:https://www.cnblogs.com/xuexidememeda/p/12430117.html