Java 基础之 volatile synchronized static automatic threadlocal

17 9月

volatile

我们知道 Java 的内存模型, 要求每一个线程会拷贝一份自己的变量副本. 因此线程之间的成员变量是不互相可见的. 这就导致线程之间的成员变量不是一致性的, 如果想要完成多线程之间变量可见, 就要用到 volatile修饰符了.

如果成员变量被 volatile 修饰的话, 它将强制线程去主内存( 唯一/线程共享 )中去拿取成员变量. 而不是改变自己的副本, 再刷回主内存.

此外 volatile 并不是原子性操作, 即使线程之间可见, 但也不能保证线程安全性. 那么如果又想保证线程可见又想保证线程安全呢? 那么就要用到 synchronized 修饰符.

synchronized

volatile 可以说是无锁, 非阻塞, 轻量级的. 而 synchronized 修饰符则可以说是有锁, 阻塞, 重量级的, 但线程安全的.synchronized 会粗狂的在拿取变量的时候加上一把线程之间的互斥锁, 当操作完成后再释放锁, 使得其他线程再拿取变量. 使得整个操作原子化.

static

前面说 volatile 会使得线程之间的成员变量互相可见, 那么每次读取的时候, 就都是最新的值了( 不考虑安全性 ). 那么这和将成员变量修饰为 static 的有什么区别? 我们知道如果被 static 修饰的话, 那么整个变量都变成了唯一的, 不都可见了吗?

简单来说, 共同性的话, 确实是表现出他们使得线程之间成员变量都”可见”. 但 static 的可见不是真正的可见, 因为它就只有一个, 谈不上和哪个线程可见.

volatile 和 JVM 内存模型有关, 只是强制将线程自己的变量刷回主内存.

而 static 是类级别的唯一性, 也就是说, 并不是每个线程有一份自己的拷贝, 大家的拷贝都强制相同, 而是压根儿大家都只有一个公用的 object. 他们的使用场景是大大不一样的.

automatic

java.util.concurrent.atomic 包下面提供了很多原子性操作的类, 里面有很多方法的变量就是用 volatile 修饰的. 从源码可以看出, 比如 AutomaticInteger 就是用了 volatile 修饰了要操作的 value. 那么为什么前面说 volatile 不保证原子性, 而仅仅用了 volatile 修饰 value 的 AutomaticInteger 又可以保证原子性操作呢?

原因是因为 AutomaticInteger 并不是在一个操作中改变 value 的值. 什么意思呢?

像 i++ 不是原子性操作, 它分为三部, 读取, 操作, 放回. 所以每一步都有可能在同一时间, 有其他的线程完成同样的操作. 导致最后放回的数据不一致. 所以解决方案无非就是 每一步操作的时候都检查一下数据是否已经被改变. AutomaticInteger 就是利用 volatile 修饰到每一步上, 单独取出来, 再操作, 再放回. 每一步都是被 volatile 修饰的. 达到了原子性操作. 当然了如果发现取出来的数据已经被改变, 就放弃这次修改, 重新拿取新值.

threadlocal

threadlocal 可以说和 synchronized 背道而驰, synchronized 就是为了将变量实现多个线程之间的本地化, 也就是每个线程拷贝一份自己的变量, 而互相不可见也不影响.  更不会刷新回主内存中.

在实际的应用场景中最大的好处就是参数的传递, 不用一直将一个参数传递下去. 如果是线程级别的, 用 threadlocal 最好不过了. 可以在任意地方 set 和 get.

发表评论

电子邮件地址不会被公开。