JVM学习笔记——内存模型篇( 三 )

有序性实现我们的可见性经常通过一种修饰词来实现:

  • volatile 修饰的变量,可以禁用指令重排
所以我们的代码经过修改后可以改造为以下代码:
int num = 0;boolean volatile ready = false;// 线程1 执行此方法public void actor1(I_Result r) {if(ready) {r.r1 = num + num;} else {r.r1 = 1;}} // 线程2 执行此方法public void actor2(I_Result r) {num = 2;ready = true;}happens-before我们在最后插入一个简单的内容happens-before:
  • 规定了哪些写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结
我们来简单介绍一些:
  1. 线程 start 前对变量的写,对该线程开始后对该变量的读可见
static int x;x = 10;new Thread(()->{ System.out.println(x);},"t2").start();
  1. 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;new Thread(()->{ x = 10;},"t1").start();new Thread(()->{ System.out.println(x);},"t2").start();
  1. 线程解锁 m 之前对变量的写 , 对于接下来对 m 加锁的其它线程对该变量的读可见
static int x;static Object m = new Object();new Thread(()->{synchronized(m) {x = 10;}},"t1").start();new Thread(()->{synchronized(m) {System.out.println(x);}},"t2").start();
  1. 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或t1.join()等待它结束)
static int x;Thread t1 = new Thread(()->{ x = 10;},"t1");t1.start();t1.join();System.out.println(x);
  1. 线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见
static int x;public static void main(String[] args) {Thread t2 = new Thread(()->{while(true) {if(Thread.currentThread().isInterrupted()) {System.out.println(x);break;}}},"t2");t2.start();new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()) {Thread.yield();}System.out.println(x);}
  1. 对变量默认值(0 , false,null)的写,对其它线程对该变量的读可见
  2. 具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z
乐观锁与悲观锁这一小节我们来详细介绍一下乐观锁和悲观锁的概念以及原型
乐观锁与悲观锁简介我们首先分别简单介绍一下乐观锁和悲观锁:
  • 乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我继续重试即可 。
  • 悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,针对共享数据直接上锁 , 只有我解锁后你们才能抢夺
我们在这里再简单讲一下两种锁的日常选用:
  • 乐观锁用于竞争不激烈且为多核CPU的情况 , 因为其实乐观锁的不断尝试需要cpu处理并且也会消耗一定内存
  • 悲观锁用于竞争激烈需要抢夺资源的情况下,我们直接停止其他操作可以减少其他不必要的内耗
乐观锁实现乐观锁的实现是采用CAS:
  • CAS 即 Compare and Swap  , 它体现的一种乐观锁的思想
我们通过一个简单示例展示:
// 需要不断尝试while(true) {int 旧值 = 共享变量 ; // 比如拿到了当前值 0int 结果 = 旧值 + 1; // 在旧值 0 的基础上增加 1,正确结果是 1/*这时候如果别的线程把共享变量改成了 5,本线程的正确结果 1 就作废了,这时候compareAndSwap 返回 false , 重新尝试,直到:compareAndSwap 返回 true,表示我本线程做修改的同时 , 别的线程没有干扰*/if( compareAndSwap ( 旧值, 结果 )) {// 成功,退出循环}}悲观锁实现乐观锁的实现是采用synchronized:
  • synchronized体现的是一种悲观锁的思想
我们通过一个简单示例展示:
package cn.itcast.jvm.t4.avo;// 我们进行操作时 , 直接上锁,不允许其他进程涉及!public class Demo4_1 {// 这里的i应该被多线程共用,设为静态变量static int i = 0;// 这里是Obj对象,我们设置它为锁,注意两个线程中的synchronized所对应的锁应该是同一个对象(锁)static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 采用synchronized设置锁实现原子性,这样i++操作就会完整进行Thread t1 = new Thread(() -> {synchronized (obj) {for (int j = 0; j < 50000; j++) {i++;}}});Thread t2 = new Thread(() -> {// 采用synchronized设置锁实现原子性,这样i--操作就会完整进行synchronized (obj) {for (int j = 0; j < 50000; j++) {i--;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);}}

推荐阅读