今天被问到这样一个问题,两个线程共用一个int类型的变量,怎么保证数据安全?(不可以用synchronized关键字)你知道原子锁吗?
1. 例子引入
public class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入到run方法中了");
while (isRunning == true) {
}
System.out.println("线程执行完成了");
}
}
public class Run {
public static void main(String[] args) {
try {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。
而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量从而出现了死循环,导致RunThread无法终止。
解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量:
volatile private boolean isRunning = true;
2. volatile
所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。
volatile就是非原子性的
比如,变量的自增操作 i++,分三个步骤:
①从内存中读取出变量 i 的值
②将 i 的值加1
③将 加1 后的值写回内存
这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。
所以仅仅使用volatile关键字并不能保证的安全。
3. AtomicInteger
这个类的源码:
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这段代码写的很巧妙:
compareAndSet方法首先判断当前值是否等于current;
如果当前值 = current ,说明AtomicInteger的值没有被其他线程修改;
如果当前值 != current,说明AtomicInteger的值被其他线程修改了,这时会再次进入循环重新比较;
使用:
AtomicInteger atomicInteger = new AtomicInteger(1);
4、java提供的原子操作
- 原子更新数组,Atomic包提供了以下几个类:
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray - 原子更新引用类型,也就是更新实体类的值,比如
AtomicReference:原子更新引用类型的值
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型 - 原子更新字段值
AtomicIntegerFieldUpdater:原子更新整形的字段的更新器
AtomicLongFieldUpdater:原子更新长整形的字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型的更新器
5、应用
Java原子操作类 AtomicInteger 在实际项目中的应用。HttpClientFacotryBean工厂会工作在多线程环境中,生成Httpclient,
就相当于建立HttpClient连接,通过工厂模式控制HttpClient连接,能够更好的管理HttpClient的生命周期。而我们使用java原子
操作类AtomicInteger来控制计数器,就是为了保证,在多线程的环境下,建立HttpClient连接不会出错,不会出现2个线程竞争一个
HttpClient连接的情况。
看到一篇说线程的文章,觉得不错,分享一下:
http://www.jianshu.com/p/40d4c7aebd66