Volatile 关键字
当多个线程操作共享数据时,可保存线程内存之间数据可见,还可防止指令重排序。相对于synchronized 是一种更为轻量级的同步策略。
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("----hello world----");
break;
}
}
}
}
class ThreadDemo implements Runnable {
// 问题变量
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
当flag不用Volatile关键字修饰输出:
flag=true
当flag使用Volatile关键字修饰输出:
----hello world----
flag=true
上面代码可以看出,如果有多条线程同时操作同享数据,如果不使用Volatile关键字修饰,就可能出现内存可见性问题。
注意:
- 1、volatile 不具备“互斥性”,不像synchronized关键字:多线程情况下当其中一条线程占据CPU资源之后,其它线程需要阻塞。
- 2、volatile 不保证变量的“原子性”。
原子性问题
原子性问题,例如:i++
int i = 10;
i = i++;
System.out.println(i);
输出结果:10
上述代码实际的底层实现:
int temp = i;
i = i + 1;
i = temp
Volatile 关键字不保证“原子性”测试
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{
// 这里变量使用Volatile关键字修饰
private volatile int serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
return serialNumber++;
}
}
输出结果:(两个相同的结果)
0
8
2
7
6
5
3
1
1
4
由上述结果
可以看到,即使变量使用Volatile关键字修饰之后,多线程环境下仍然有出现问题的可能性。
如何解决原子性问题
原子变量:JDK1.5以后 java.util.concurrent.atomic 包下提供有原子变量。
- 1、使用Volatile关键字保证变量内存可见性
2、使用CAS(Compare-And-Swap)算法保证变量原子性。CAS算法是硬件对于并发操作共享数据的支持。
CAS 包含三个操作数:- 内存值 V
- 预估值 A
- 更新值 B
当且仅当 V == A 时,V = B。否则,则不做任何操作。
public class Test {
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 AtomicInteger serialNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
// 自增
return serialNumber.getAndIncrement();
}
}
输出结果:(无论多少次操作结果都不会出现有相同值的情况)
6
9
0
2
3
5
4
7
1
8
模拟CAS算法:
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 设置之前先获取一下内存值
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
// 内存值
private int value;
/** * 获取内存值 * @return */
public synchronized int get(){
return value;
}
/** * 比较 * @param expectedValue 预估值 * @param newValue 新值 * @return */
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
// 内存值和预估值比较
if(oldValue == expectedValue){
// 如果内存值和预估值相同,就把新值赋值给内存值
this.value = newValue;
}
// 返回旧内存值
return oldValue;
}
/** * 设置新值 * @param expectedValue 预估值 * @param newValue 新值 * @return 设置结果 */
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
输出结果:(有成功有失败)
true
false
true
false
false
true
false
false
true
false