为何要使用原子变量呢,原因是多个线程对单个变量操作也会引起一些问题。在Java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但这样太麻烦。
Java5之后,专门提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也很简单。
Atomic类的作用
- 使得让对单一数据的操作,实现了原子化
- 使用Atomic类构建复杂的,无需阻塞的代码访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子
package com.thread.atomic; import java.util.concurrent.atomic.AtomicLong; /** * 原子性 */ import java.util.concurrent.locks.Lock; public class MyCount extends Thread{ private static AtomicLong cash = new AtomicLong(1000); // 账户余额 <一定要是static> private Lock lock ;//不公平锁 private int x; private String name; MyCount(Lock lock, String name, int x) { this.lock = lock; this.x = x; this.name = name; } @Override public void run() { lock.lock(); cash.addAndGet(x); System.out.println(name + "执行了 " + x + ",当前余额:" + cash.get()); lock.unlock(); } }
package com.thread.atomic; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestAtomic { private Lock lock = new ReentrantLock(false);//不公平锁 public void run() { ExecutorService pool = Executors.newCachedThreadPool(); MyCount c1 = new MyCount(lock, "张三", 100); MyCount c2 = new MyCount(lock, "郭树", -200); MyCount c3 = new MyCount(lock, "张总", 400); MyCount c4 = new MyCount(lock, "胖子", -100); MyCount c5 = new MyCount(lock, "小李", -100); MyCount c6 = new MyCount(lock, "校长", 400); MyCount c7 = new MyCount(lock, "王总", -500); pool.execute(c1); pool.execute(c2); pool.execute(c3); pool.execute(c4); pool.execute(c5); pool.execute(c6); pool.execute(c7); pool.shutdownNow(); } public static void main(String[] args) { new TestAtomic().run(); } }
private static AtomicLong cash = new AtomicLong(1000); // 账户余额 <一定要是static>
- 不加static的话每个人的操作全是针对10000这个值而言的,这几个线程都在别的线程还没改变10000这个值的时候做的操作,还是每个线程对along 的值的修改无法对其他线程生效 .
- static :static变量是属于类的,不是某一个对像,也就是所有对像共用的变量,非static就是一个对像一个
Volatile的实现原理
那么Volatile是如何来保证可见性的呢?在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。
Java代码: | instance = new Singleton();//instance是volatile变量 |
汇编代码: | 0x01a3de1d: movb $0x0,0x1104800(%esi); 0x01a3de24: lock addl $0x0,(%esp); |
- 将当前处理器缓存行的数据会写回到系统内存。
- 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。