在学习并发编程的时候一定要了解Volatile关键字和Atomic类。现在让我们来逐一了解。
要想详细了解Volatile关键字就必须了解一下线程之间的可见性,什么是线程可见性呢?
可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程的修改结果,另一个线程马上能看到。下面举一个代码列子来说明:
public class UseVolatitle extends Thread {
private boolean isrunning = true;
public void setIsrunning(boolean isrunning) {
this.isrunning = isrunning;
}
@Override
public void run() {
System.out.println("开始启动线程");
while(isrunning) {
}
System.out.println("线程结束");
}
public static void main(String[] args) throws Exception {
// 新建类实例
UseVolatitle useVolatitle = new UseVolatitle();
// 启动线程方法
useVolatitle.start();
// 线程睡眠2000毫秒,也就是2分钟。
Thread.sleep(2000);
// 把线程中isrunning值设置为false
useVolatitle.setIsrunning(false);
System.out.println("主线线程将running设置为false");
}
}
该程序的运行结果如下:
开始启动线程
主线线程将running设置为false
虽然打印出了两条语句但是控制台没有停止,死循环依然卡在哪里。也就是主线程对相关线程isrunning的值改写,没有被相关线程可见,当试图把变量加上volatile关键字。代码如下:
public class UseVolatitle extends Thread {
private volatile boolean isrunning = true;
public void setIsrunning(boolean isrunning) {
this.isrunning = isrunning;
}
@Override
public void run() {
System.out.println("开始启动线程");
while(isrunning) {
}
System.out.println("线程结束");
}
public static void main(String[] args) throws Exception {
UseVolatitle useVolatitle = new UseVolatitle();
useVolatitle.start();
Thread.sleep(2000);
useVolatitle.setIsrunning(false);
System.out.println("主线线程将running设置为false");
}
}
运行结果如下:
开始启动线程
主线线程将running设置为false
线程结束
再次运行,虽然卡着几秒钟,但最终程序能够正常终止。这就是volatile作用。
Atomic类就是为了实现原子性,主要的核心类有:1、AtomicInteger;2、AtomicLong;3、AtomicBoolean;4、AtomicIntegerArray;5、AtomicLongArray;6、AtomicReference。首先了解一下原子性。
原子性操作的最小单位,例如转账业务:从农业银行转1块钱到工商银行。首先从用户的农业银行A帐号扣除1块钱,然后再向用户所在的工商银行帐号B新增1块钱。这两个操作要么同时成功,要么同时失败。下面举一个代码例子来深入了解Atomic类来操作原子性。
public class NoUseAtomic {
static int count = 0;
public int add() {
count = count+10;
return count;
}
public static void main(String[] args) {
NoUseAtomic noUseAtomic = new NoUseAtomic();
List list = new ArrayList();
for(int i = 0; i < 100; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(noUseAtomic.add());
}
}));
}
for(Thread t : list) {
t.start();
}
}
}
控制台输出结果如下:
10
20
30
40
......
960
如果你仔细观察,你会发现那个该死1000没有出现,虽然这100个线程执行先后顺序我们是不可预测的,但是我总是希望在某个线程的某个时刻输出期待已久的1000,但现实总是令人悲伤的。现在让我们来给我们的count变量替换Atomic类,使之能出现我们预期的结果。相关代码如下:
public class UseAtomic {
static AtomicInteger count = new AtomicInteger(0);
public int add() {
count.addAndGet(10);
return count.get();
}
public static void main(String[] args) {
UseAtomic noUseAtomic = new UseAtomic();
List list = new ArrayList();
for(int i = 0; i < 100; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(noUseAtomic.add());
}
}));
}
for(Thread t : list) {
t.start();
}
}
}
运行上诉代码,虽然输出的先后顺序也同样是不可预期的,但是在众多的输出中,你会发现1000被打印出来。
注:(1)并非所有的Java基本数据类型都具有其原子类,比如并不存在AtomicChar,AtomicFloat和AtomicDouble等。
(2)虽然Atomic是原子性的,但是add方法并发原子性的。
1.1讲到add方法并发原子性,举代码例子说明如下:
public class WarmingInAtomic {
static AtomicInteger count = new AtomicInteger(0);
public int add() {
count.addAndGet(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
return count.get();
}
public static void main(String[] args) {
WarmingInAtomic warmingInAtomic = new WarmingInAtomic();
List list = new ArrayList();
for(int i = 0; i < 100; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(warmingInAtomic.add());
}
}));
}
for(Thread t : list) {
t.start();
}
}
}
运行的部分结果如下:
109
118
127
136
145
154
163
172
运行结果之间的最小间隔是1,这就是并发原子性造成的。如果方法上加上关键字synchronized,则保持原子性,相关代码如下:
public class WarmingInAtomic {
static AtomicInteger count = new AtomicInteger(0);
public synchronized int add() {
count.addAndGet(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
count.addAndGet(1);
return count.get();
}
public static void main(String[] args) {
WarmingInAtomic warmingInAtomic = new WarmingInAtomic();
List list = new ArrayList();
for(int i = 0; i < 100; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(warmingInAtomic.add());
}
}));
}
for(Thread t : list) {
t.start();
}
}
}
输出部分结果如下:
10
20
30
40
50
60
70
80
90
100
当我们在主线程利用其它线程修改类属性,输出一直读的是主线程的类属性。相关代码如下:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class UseAtomicRefrence1 {
static Person person;
public static void main(String[] args) throws InterruptedException {
UseAtomicRefrence1 uar = new UseAtomicRefrence1();
person = new Person();
person.setName("root");
person.setAge(18);
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
t1.join();
t2.join();
Thread.sleep(1000);
System.out.println("now value : " + person.toString());
}
static class Task1 implements Runnable {
@Override
public void run() {
person.setAge(19);
person.setName("admin");
System.out.println("thread1 value : " + person.toString());
}
}
static class Task2 implements Runnable {
@Override
public void run() {
person.setAge(20);
person.setName("super user");
System.out.println("thread2 value : " + person.toString());
}
}
}
这样做是不能保证数据的一致性的,控制台输出如下:
thread1 value : Person{name='admin', age=19}
thread2 value : Person{name='super user', age=20}
now value : Person{name='super user', age=20}
在类相应的对象初始化时用上AtomicReference属性,相关代码如下:
public class UseAtomicRefrence2 {
//普通引用
static Person person;
//原子性引用
static AtomicReference aPerson;
public static void main(String[] args) throws InterruptedException {
UseAtomicRefrence2 uar = new UseAtomicRefrence2();
//new一个person类
person = new Person();
person.setName("root");
person.setAge(18);
//new一个AtomicReference原子性引用
aPerson = new AtomicReference<>(person);
System.out.println("启动其他线程之前主线程的值:" + aPerson.get());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
t1.join();
t2.join();
Thread.sleep(1000);
System.out.println("主线程当前值 : " + person.toString());
}
static class Task1 implements Runnable {
@Override
public void run() {
Person expect = aPerson.get();
Person update = new Person("admin", 19);
//cas原子性操作
aPerson.compareAndSet(expect, update);
System.out.println("thread1 value : " + aPerson.get());
}
}
static class Task2 implements Runnable {
@Override
public void run() {
Person expect = aPerson.get();
Person update = new Person("super user", 20);
//cas原子性操作
aPerson.compareAndSet(expect, update);
System.out.println("thread2 value : " + aPerson.get());
}
}
}
控制台输出如下:
启动其他线程之前主线程的值:Person{name='root', age=18}
thread1 value : Person{name='admin', age=19}
thread2 value : Person{name='super user', age=20}
主线程当前值 : Person{name='root', age=18}
从上面的控制题看出,主线程,thread1,thread2线程分别对person对象的设置能在各自的线程中打印出来,并且thread1,thread2对静态变量的修改对主线程的对象属性值没有影响,保证了线程的安全。