wordpress问卷模板下载,seo搜索引擎优化排名,国外网站icp备案,七牛云wordpress缓存附件1、简单介绍一下JMM
Java 内存模型#xff08;Java Memory Model 简称JMM#xff09;是一种抽象的概念#xff0c;并不真实存在#xff0c;指一组规则或规范#xff0c;通过这组规范定义了程序中各个变量的访问方式。java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问…1、简单介绍一下JMM
Java 内存模型Java Memory Model 简称JMM是一种抽象的概念并不真实存在指一组规则或规范通过这组规范定义了程序中各个变量的访问方式。java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异以实现让java程序在各种平台下都能达到一致的并发效果。 JMM规定
所有的共享变量都存储在主内存中包括实例变量、类变量静态变量但是不包括局部变量因为局部变量是线程私有的不存在多线程之间的竞争每个线程都有自己的工作内存线程工作内存中保留了被线程使用的共享变量的副本线程对变量的操作读或者写都必须在工作内存中完成不能直接操作主内存不同线程之间不能相互访问对方的工作内存线程间变量值的传递需要通过主内存完成。
工作内存和主内存的关系图 2、分析一下共享变量的不可见性问题。
看代码定义了一个成员变量 falg一个子线程负责修改flag的值另外一个子线程根据flag的值判断是否跳出空循环实际执行结果为下图可见线程0对flag的修改并没有影响到线程1这就是多线程下共享变量的修改会存在不可见性。
原因就是Thread-1一直访问的都是自己本地内存中的flag而没有从主内存中去更新flag所以没办法跳出循环。 public class TestVolatile {// 定义一个成员变量static boolean flag true;public static void main(String[] args) {new Thread(() - {System.out.println(Thread.currentThread().getName() start);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}flag false;System.out.println(Thread.currentThread().getName() end);}).start();new Thread(() - {System.out.println(Thread.currentThread().getName() start);while (flag) {// 空转}System.out.println(Thread.currentThread().getName() end);}).start();}
} 3、解决共享变量可见性的两种方式
volatile关键字加锁
3.1、volatile关键字处理还是上述代码只要在flag属性前加一个volatile关键字就可以了。 3.2、 加锁处理看代码
线程1赋值修改flag线程2不断读取flag可以看到flag被线程1修改后线程2是可以读取到变化之后的结果的。
public class TestVolatile {// 定义一个成员变量static boolean flag true;static final Object lock new Object();public static void main(String[] args) {test02(lock);}private static void test02(Object lock) {new Thread(() - {System.out.println(Thread.currentThread().getName() start);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}flag false;System.out.println(Thread.currentThread().getName() end);}).start();new Thread(() - {System.out.println(Thread.currentThread().getName() start);while (true) {synchronized (lock) {if (flag) {System.out.println(flag flag);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}} else {System.out.println(flag flag);break;}}}System.out.println(Thread.currentThread().getName() end);}).start();}
}
执行结果 4、解决共享变量可见性的原理
加锁某一个线程进入synchronized代码块后执行过程如下线程获得锁清空工作内存从主内存中拷贝最新值到工作内存执行代码修改后的副本值刷新回主内存线程释放锁。volatile关键字其实还是工作内存的及时刷新volatile有以下语义 写一个volatile变量时JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中读一个volatile变量时JMM会把该线程本地内存设为无效重新回到主内存中读取最新共享变量。 5、volatile不保证原子性
volatile是不保证原子性操作的。 static volatile int count 0;public static void main(String[] args) throws InterruptedException {for (int i 0; i 10; i) {new Thread(() - {for (int j 0; j 10000; j) {count;}System.out.println(Thread.currentThread().getName() count count);}).start();}}
如上述代码定义了一个volatile修饰的int类型变量启动10个线程去执行操作每个线程修改10000次按理说修改后的值应该为100000但是每次执行的结果都没有到100000 发生上述问题的原因在于count这个操作不是原子性的他包含三个步骤
从主内存中读取数据导工作内存对工作内存中的数据进行1操作将工作内存中的数据写会到主内存
假设某一时间两个线程都执行到了步骤1读取到的count值是100 然后线程1的CPU时间片到了停止执行此时2线程继续执行23步骤将主内存的值修改为101这个时候线程1继续执行但是因为1已经执行了没有重新去主内存中取值因此执行23操作后新值为101然后往主内存修改的值也是101。
解决原子性办法
1、加锁 static final Object lock new Object();static volatile int count 0;public static void main(String[] args) throws InterruptedException {for (int i 0; i 10; i) {new Thread(() - {for (int j 0; j 10000; j) {synchronized (lock){count;}}System.out.println(Thread.currentThread().getName() count count);}).start();}} 2、使用atomic包 // 底层CAS不多介绍了。 static AtomicInteger a new AtomicInteger();public static void main(String[] args) throws InterruptedException {for (int i 0; i 10; i) {new Thread(() - {for (int j 0; j 10000; j) {a.getAndIncrement();}System.out.println(Thread.currentThread().getName() count a);}).start();}} 6、指令重排序介绍。
什么是重排序为了提高性能编译器和处理器常常会对指令进行重新排序。一般重排序分为以下三种编译器优化重排序指令级并行重排序内存系统重排序。 重排序就是为了提高处理初度如下图。 6.1、指令重排序在多线程并发下会产生什么问题
经典案例以下代码执行完毕i 和 j 的值有可能是多少经过测试i0,j0的情况也会出现这就是指令重排序导致的问题 import java.util.concurrent.CountDownLatch;public class T01_Disorder {private static int x 0, y 0;private static int a 0, b 0;public static void main(String[] args) throws InterruptedException {for (long i 0; i Long.MAX_VALUE; i) {x 0;y 0;a 0;b 0;CountDownLatch latch new CountDownLatch(2);Thread one new Thread(new Runnable() {public void run() {a 1;x b;latch.countDown();}});Thread other new Thread(new Runnable() {public void run() {b 1;y a;latch.countDown();}});one.start();other.start();latch.await();String result 第 i 次 ( x , y ;if (x 0 y 0) {System.err.println(result);break;}}}}7、volatile是怎么保证指令执行顺序的
jvm级别识别到volatile关键词会执行jvm内存屏障包括 loadload 屏障、storestore 屏障、loadstore屏障、storeload 屏障其中load是读store是写
a) 会在写之前加 storestore写之后加storeload保证在自己写之前完成其他的写在自己写完之后才能继续其他的读
b) 会在读之后加上loadload 和 loadstore 保证在自己读完之后其他的才能读自己读完之后其他的才能写 8、 Happens Before原则
简单的说如果 A Happens Before B 那么A的操作对B都是可见的。 Happens Before模型是由8条具体规则组成的
程序顺序规则单线程中每个操作 都 Happens Before 他后面的操作。监视器规则一个线程解锁 Happens Before 后面线程的加锁volatile变量规则对一个volatile变量的写 Happens Before 对这个volatile的读传递规则A Happens Before BB Happens Before C则 A Happens Before Cstart() 规则如果线程A执行ThreadB.start()那么A线程的ThreadB.start()操作 happens-before 线程B的任意操作。join() 规则如果线程A执行ThreadB.join()那么B线程中的任意操作 happens-before 线程A从ThreadB.join()成功返回。程序中断规则对线程interrupted()方法的调用 happens-before 被中断线程的代码检测到中断时间的发生对象finalize规则一个对象初始化完成构造函数执行结束happens-before 于发生它的finalize()方法的开始 9、总结一下volatile关键字的作用
保证变量的可见性禁止指令重排序 10、volatile和synchronized的区别
关键字使用范围volatile只能修饰变量synchronized关键字能修饰变量方法代码块是否会阻塞线程volatile不会阻塞线程synchronized会阻塞线程原子性volatile不保证原子性synchronized可以保证原子性可见性volatile 和 synchronized 都可以保证 修改的可见性指令重排序volatile禁止指令重排序synchronized允许被编译器优化。
总的来说volatile的本质是告诉JVM,变量在工作内存寄存器中的值是不确定的需要从主存中去取synchronized则是直接锁住当前变量只有当前线程可以访问其他线程阻塞。 11、单例模式中使用了双重判断为什么还需要volatile关键字
synchronized关键字可以保证原子性和可见性但是没有办法保证顺序性即可以被指令重排序new 一个对象可以分为以下三个步骤
为对象分配内存空间初始化对象对象地址的引用
假设new 对象的过程发生了指令重排序步骤2和3互换。虽然创建对象加锁了但是加锁线程1执行完1和3之后失去了CPU时间片此时初始化对象还没有完成这个时候线程2执行在外层判断的时候INSATNCE!null, 直接就获取对象执行操作了但是因为该对象实际还没有初始化完成呢因此线程2返回的就是一个空对象。
而volatile关键字禁止指令重排序就避免了上述问题。 public static T01 getInstance(){private static volatile T01 INSTANCE;private T01 (){//私有构造器外部不能new}if(INSTANCEnull){synchronized (T01.class){if(INSTANCEnull){INSTANCEnew T01();}}}return INSTANCE;}