1 第一章 并发编程基础
1.3 Volatile及原子性
Volatile概念
Volatile关键字的主要作用是使变量在多个线程间可见。在多线程间可以进行变量的变更,使得线程间进行数据的共享可见。阻止指令重排序,happens-before
- volatile的作用就是强制线程到主内存(共享内存)里去读取变量,而不去线程工作内存区里去读取,从而实现了多个线程间的变量可见。也就是满足线程之间的可见性。
通过下面的源代码及运行结果可以大概了解一下volatile的用法。
public class UseVolatile extends Thread {
private volatile boolean isRunning = true;
private void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
public void run(){
System.out.println("进入run方法...");
while(isRunning == true) {
//....
}
System.err.println("线程停止!");
}
public static void main(String[] args) throws InterruptedException {
UseVolatile uv = new UseVolatile();
uv.start();
Thread.sleep(2000);
uv.setRunning(false); //修改isRunning = false
System.out.println("isRunning的值已经被设置成了false!");
}
}
Volatile内存模型如下图:
Happens-before
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,在这个例子就是A操作的结果要对B操作可见,那么必然存在A happens-before B.
指令的重排序
JAVA语言为了维持顺序内部的顺序化语义,也就是为了保证程序的最终运行结果需要和在单线程严格意义的顺序化环境下执行的结果一致,程序指令的执行顺序有可能和代码的顺序不一致,这个过程就称之为指令的重排序。指令重排序的意义在于:JVM能根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能!
Atomic
Atomic系列类封装了一系列的基础类型和对象操作,其主要目的就是为了实现原子性,主要核心类如下:
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicIntegerArray
- AtomicLongArray
- AtomicReference
从下面的例子来看看保证原子性的必要性。
import java.util.ArrayList;
import java.util.List;
public class UseAtomicInteger {
private static int count=0;
public synchronized int add() {
count += 10;
return count;
}
public static void main(String[] args) {
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(new Thread( new Runnable() {
@Override
public void run() {
System.err.println("累计结果:" + (new UseAtomicInteger()).add());
}
}));
}
for (Thread thread : list) {
thread.start();
}
}
}
多运行几次后,会发现输出为10(或者别的数字)的有2条,输出为200的没有。
为了保证原子性,采用AtomicInteger来替代普通的int类型。下面是改进后的代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class UseAtomic {
private static AtomicInteger count = new AtomicInteger(0);
public synchronized int add() {
count.addAndGet(10);
return count.get();
}
public static void main(String[] args) {
UseAtomic ua = new UseAtomic();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(new Thread( new Runnable() {
@Override
public void run() {
System.err.println("累计结果:" + ua.add());
}
}));
}
for (Thread thread : list) {
thread.start();
}
}
}
这里是输出结果:
如果操作对象不是简单数据类型,而是对象类型,可以使用AtomicReference。
下面给出一个例子。
import java.util.concurrent.atomic.AtomicReference;
public class UseAtomicReference {
private static Person person;
private static AtomicReference<Person> personRfer;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
personRfer = new AtomicReference<Person>(person);
System.out.println("Person is " + personRfer.get());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
t1.join();
t2.join();
Thread.sleep(100);
System.out.println("Now Person is " + personRfer.get());
}
static class Task1 implements Runnable {
public void run() {
Person person=new Person("Rose",19);
personRfer.getAndSet(person);
System.out.println("Thread1 Values " + personRfer.get());
}
}
static class Task2 implements Runnable {
public void run() {
Person person=new Person("Sophia",20);
personRfer.getAndSet(person);
System.out.println("Thread2 Values " + personRfer.get());
}
}
}