java中volatile关键字的介绍(个人使用-介绍从简)
1、volatile关键字修饰共享变量,所以变量在多线程中的可见性。
- 实现共享变量的可见性,保证数据一致性,就实现了同步机制,但是volatile没有给变量加锁。所以volatile是轻量级的同步机制。
- 当然synchronizied关键字给变量加锁,保证变量的同步。浪费性能
- volatile可见性的原理:被volatile修饰的变量,在编译的时候,源代码对这个变量的读写操作的语句的前后,会被插入内存屏障,禁止了一些处理器对指令的重排序,所以能保证对这个变量的写操作总是在读操作之前完成。
- volatile保证可见性的过程:线程取一个变量的值,为了提高存取的速率,一般会把变量从系统主内存中读取到线程的本地内存中,如果线程中改了这个变量的值,那么有时候更改后的值不能及时的写进主内存,而且有的线程是一起取得值,就算写到主内存中,其他线程也不知道,这就造成数据不一致。但是使用了volatile修饰的变量,一个线程从改变变量的值后,会立即更新主内存,利用多处理器的缓存一致性,其他处理器会发现自己的缓存行对应的内存地址被修改,就会之前获取的数据设为无效,重新获取。
- volatile保证的是可见性,不能实现互斥和原子操作。互斥可以 加锁、信号量机制(semphore)实现。
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
if (td.isFlag()) {
System.out.println("########");
break;
}
//System.out.println("1111");
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
public void run() {
try {
// 该线程 sleep(200), 导致了main中的#####一直无法输出
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
上面的程序中的main函数的######是不能输出的,因为可见性问题。
原因:首先因为新线程sleep(200)所以main取到的flag=false; 但是因为main中的while(true)里面直接就一个if(td.isFlag()),所以main主线程没时间去更新flag的值,只能从主线程的缓存中取,还是false。
- 解决一:如果在main的while中添加其他的语句,那么if(td.isFlag())就不那么繁忙了,就 有喘息的时间去系统主内存更新flag的值了。
- 解决二:加锁,这个方法 可能和第一个原因一样,每次main线程获取锁对象的时候给它时间去更新flag的值。当时浪费系统性能
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
synchronized (td) {
if (td.isFlag()) {
System.out.println("########");
break;
}
}
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
public void run() {
try {
// 该线程 sleep(200), 导致了main中的#####一直无法输出
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.interrupted();
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
*解决三:用volatile修饰flag变量
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
if (td.isFlag()) {
System.out.println("########");
break;
}
}
}
}
class ThreadDemo implements Runnable {
private volatile boolean flag = false;
public void run() {
try {
// 该线程 sleep(200), 导致了main中的#####一直无法输出
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.interrupted();
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
2、虽然volatile不具有让数据保持原子性,但是java.util.concurrent.atomic包下提供了一系列的原子变量,这些原子的值就是用volatile修饰来实现的原子操作
- 下面的第一个代码就有问题,因为serialNumber++;不是一个原子操作
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
private int serialNumber = 0;
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}
- 解决方法:使用java.utils.concurrent.automic提供的原子变量操作(用volatile的可见性实现)
import java.util.concurrent.atomic.AtomicInteger;
class AtomicDemo implements Runnable {
private AtomicInteger serialNumber = new AtomicInteger();
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
public int getSerialNumber() {
// 自增运算
return serialNumber.getAndIncrement();
}
}
3、简单介绍volatile的注意事项
- 首先锁的特性有两个:互斥性、可见性。
- 互斥性保证一次只能一个线程访问,可见性保证,在第一个线程释放锁后,第二个线程获取锁进入共享代码执行的时候,现在得到的变量的值或者之前就获得的值是最新的值。
- 首先volatile只能实现变量的可见性,但是有可见性也不一定就能保证同步,因为volatile没有原子性。
- 比如实现计数器的时候:(i++) 这个操作,假如两个线程对volatile i ;具体不知道咋说,反正知道这个有问题就行了,必须加锁才能解决,或者用juc中的原子操作类