中年|揭秘JMM、Synchronized、Volatile之间的关系( 三 )


被final修饰的字段在构造器中一旦被初始化完成 , 并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情 , 其他线程有可能通过这个引用访问到“初始化了一半”的对象) , 那么在其他线程中就能看见final字段的值 。1.3.3 有序性
一句话总结的话就是
如果在本线程内观察 , 所有的操作都是有序的;如果在一个线程中观察另一个线程 , 所有的操作都是无序的 。问题来了 , 为什么在一个线程观察另一个线程的时候 , 操作都是无序的呢?
这就涉及到了指令重排:你写的程序 , 计算机并不是按照你写的那样去执行的 , 我们可以举个例子来说明
int x = 1 // 1int y = 2 // 2x = x + 5 // 3y = x * x // 4
我们期望的程序执行顺序是1-&gt2-&gt3-&gt4 , 我们发现如果程序是2-&gt1-&gt3-&gt4执行结果也是一样的 , 或者1-&gt3-&gt2-&gt4也行 , 但是如果按照1—&gt2-&gt4-&gt3之类的呢?就得不到我们的期望结果 , 这就是指令重排导致的 。
Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性 , volatile关键字本身就包含了禁止指令重排序的语义 , 而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的 , 这个规则决定了持有同一个锁的两个同步块只能串行地进入 。
但是如果所有的有序性都靠这两个关键字来完成的话 , 那么很多操作就会变得特别啰嗦 , 所以就有了一个Happens-Before原则 。
1.4 Happens-Before原则
程序次序规则(Program Order Rule):在一个线程内 , 按照控制流顺序 , 书写在前面的操作先行发生于书写在后面的操作 。 注意 , 这里说的是控制流顺序而不是程序代码顺序 , 因为要考虑分支、循环等结构 。
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作 。 这里必须强调的是“同一个锁” , 而“后面”是指时间上的先后 。 ·volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作 , 这里的“后面”同样是指时间上的先后 。
线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作 。

线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测 , 我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行 。
线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生 , 可以通过Thread::interrupted()方法检测到是否有中断发生 。
对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始 。
传递性(Transitivity):如果操作A先行发生于操作B , 操作B先行发生于操作C , 那就可以得出操作A先行发生于操作C的结论 。
2 关于Synchronized关键字
上面我们介绍了JMM的特性的时候 , 也了解到了Synchronized是一个比较全能的同步块 , 可以保证很多的特性 。
synchronized块是Java提供的一种原子内置锁 , Java中的每个对象都可以把它当作一个同步块来使用 , 这些Java内置的使用者看不到的锁被称为内部锁 , 也叫做监视器锁 。 程的执行代码在进入synchronized代码块前会自动获取内部锁 , 这时候其他线程访问该同步代码块时会被阻塞挂起 。 拿到内部锁的线程会在正常退出同步代码块或者抛出异后或者在同步块内调用了该内置锁资源的wai t系列方法时释放该内置锁 。 内置锁是排它锁 , 也就是当一个线程获取这个锁后 , 其他线程必须等待该线程释放锁后才能获取该锁 。2.1 synchronized的内存语义使用synchronized可以解决共享变量内存可见性的问题 。synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除 , 这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取 , 而是直接从主内存中获取 。 退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存 。 其实这也是加锁和释放锁的语义 , 当获取锁后会清空锁块内本地内存中将会被用到的共享变量 , 在使用这些共享变量时从主内存进行加载 , 在释放锁时将本地内存中修改的共享变量刷新到主内存 。除可以解决共享变量内存可见性问题外 , synchronized经常被用来实现原子性操作 。 另外请注意 , synchronized关键字会引起线程上下文切换并带来线程调度开销 。3 关于Volatile关键字


推荐阅读