科技报道|一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂( 二 )


private volatile int ioRatio = 50;【科技报道|一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂】这里为什么要用 volatile 修饰呢?我们首先对 volatile 关键字进行说明 , 然后再结合 Netty 的代码进行分析 。
关键字 volatile 是 Java 提供的最轻量级的同步机制 , Java 内存模型对 volatile 专门定义了一些特殊的访问规则 。 下面我们就看它的规则 。 当一个变量被 volatile 修饰后 , 它将具备以下两种特性 。

  • 线程可见性:当一个线程修改了被 volatile 修饰的变量后 , 无论是否加锁 , 其他线程都可以立即看到最新的修改 , 而普通变量却做不到这点 。
  • 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果 , 而不能保证变量赋值操作的顺序与程序代码的执行顺序一致 。 举个简单的例子说明下指令重排序优化问题 , 代码如下:
public class ThreadStopExample {private static boolean stop;public static void main(String[] args) throws InterruptedException {Thread workThread = new Thread(new Runnable() {public void run() {int i= 0;while (!stop) {i++;try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}});workThread.start();TimeUnit.SECONDS.sleep(3);stop = true;}}我们预期程序会在 3s 后停止 , 但是实际上它会一直执行下去 , 原因就是虚拟机对代码进行了指令重排序和优化 , 优化后的指令如下:
if (!stop)While(true)......workThread 线程在执行重排序后的代码时 , 是无法发现变量 stop 被其它线程修改的 , 因此无法停止运行 。 要解决这个问题 , 只要将 stop 前增加 volatile 修饰符即可 。 volatile 解决了如下两个问题 。 第一 , 主线程对 stop 的修改在 workThread 线程 中可见 , 也就是说 workThread 线程 立即看到了其他线程对于 stop 变量 的修改 。 第二 , 禁止指令重排序 , 防止因为重排序导致的并发访问逻辑混乱 。
一些人认为使用 volatile 可以代替传统锁 , 提升并发性能 , 这个认识是错误的 。 volatile 仅仅解决了可见性的问题 , 但是它并不能保证互斥性 , 也就是说多个线程并发修改某个变量时 , 依旧会产生多线程问题 。 因此 , 不能靠 volatile 来完全替代传统的锁 。 根据经验总结 , volatile 最适用的场景是 “ 一个线程写 , 其他线程读 ” , 如果有多个线程并发写操作 , 仍然需要使用锁或者线程安全的容器或者原子变量来代替 。 下面我们继续对 Netty 的源码做分析 。 上面讲到了 ioRatio 被定义成 volatile , 下面看看代码为什么要这样定义 。
final long ioTime = System.nanoTime() - ioStartTime;runAllTasks(ioTime * (100 - ioRatio) / ioRatio);通过代码分析我们发现 , 在 NioEventLoop 线程 中 , ioRatio 并没有被修改 , 它是只读操作 。 既然没有修改 , 为什么要定义成 volatile 呢?继续看代码 , 我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法 。
public void setIoRatio(int ioRatio) {if (ioRatio <= 0 || ioRatio > 100) {throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)");}this.ioRatio = ioRatio;}首先 , NioEventLoop 线程 没有调用该 set 方法 , 说明调整 IO 执行时间比例 是外部发起的操作 , 通常是由业务的线程调用该方法 , 重新设置该参数 。 这样就形成了一个线程写、一个线程读 。 根据前面针对 volatile 的应用总结 , 此时可以使用 volatile 来代替传统的 synchronized 关键字 , 以提升并发访问的性能 。


推荐阅读