JAVA线程安全的原子性
众所周知,原子(atom)是在化学反应中不可再分的基本微粒,也就是原子在化学反应中不可再分割。不过我们今天不讲化学?,我们今天讲一下线程安全中的原子性。首先我们来了解一下线程安全的概念。
1. 什么是线程安全
线程安全可以简单地理解为一个方法或功能被应用到多线程环境中使用时不会出现错误的结果。
从反面来看,什么是线程不安全呢?
例如下面两段代码:
public class UnsafeSequence {
private int unsafe_Variable;
//变量值自增1
public int getNext(){
return unsafe_Variable++;
}
}
public class test {
public static void main(String[] args) {
UnsafeSequence sequence = new UnsafeSequence();
//创建线程1
Thread thread1 = new Thread("thread1"){
@Override
public void run() {
while (true){
//输出当前线程名和当前的变量值
System.out.println(this.getName()+":"+sequence.getNext());
try {
//让当前线程休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//创建线程2
Thread thread2 = new Thread("thread2"){
@Override
public void run() {
while (true){
System.out.println(this.getName()+":"+sequence.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread1.start();
thread2.start();
}
}
控制台输出:
thread1:0
thread2:1
thread1:2
thread2:3
thread2:4 这里开始发生错误啦!!!
thread1:4
可以看到错误输出结果,两个线程均获取到:4。
这是由于在其中一个线程正在写入变量值的时候,该变量尚未完成赋值操作("i++"实现的操作步骤是:读取i->自增i -> 赋值i);
所以此时的变量值依然是上一个值:3;
在被另外一个线程读取该变量值时,实现自增,依然是对原来的变量值3进行自增操作;
最后导致两个线程写入的变量值都是4,这样就解释了为什么会出现这个结果。
如果我说得不清楚,可以参照下图:
当然箭头的位置并不是绝对的,只要保证在Thread2往变量中写入更新后的值之前,Thread1读取了变量的值,那么就会发生错误的结果。
想要把上面的程序变成线程安全的,操作其实很简单:
我们不需要你知道如何利用synchronized和lock如何进行手动实现,只需要知道JAVA中封装了一个线程安全的类,保证了操作的原子性(下面就介绍什么是原子性啦)。
这个类是AtomicInteger,我们用它取代 int 基本数据类型。
public class SafeSequence{
private AtomicInteger safe_Variable = new AtomicInteger(0);
public int getNext(){
//自增1且获取更新后的值
return safe_Variable.getAndIncrement();
}
}
控制台输出:
thread2:0
thread1:1
thread2:2
thread1:3
thread2:4
thread1:5
thread1:6
2.原子性
在文章最开头我们已经知道了原子在化学反应中是不可分割的,那么从化学领域迁移过来,在多线程的领域中原子性代表一组操作不可分割。也就是说一组操作要么全部执行完,要么就不执行。
下面我会拿上面错误例子中的一个操作说明什么是原子性。
i++;
此操作实际上在JVM(JAVA VIRTUAL MACHINE)中分成3步来完成
1.读取变量i的值 |
---|
2.值自加1 |
3.将值赋予变量i |
在自增操作中的任何一步小操作过程中,如果有人篡改了变量的值,那么就会出现我们不希望出现的结果。
所以,为了保证线程安全,原子性是必不可少的一点(当然还有许多因素)。
3. 生活中的应用
你有没有想到在我们生活中有哪些操作运用了原子性呢?提示一下:?
答:是转账服务。转账服务里运用了事务
事务确保所有操作可以顺利完成后才进行真正的确认操作,称为提交事务;否则前面已经执行的操作也要被撤回,称为事务回滚。
支付宝、微信、银行卡付款我们天天都在用。转账就是从A账户扣款,往B账户加款。
这个过程是原子性的。要么转账成功,要么转账失败。总不能说扣了我钱,你又没收到钱,那收款的和付款的估计都要被气炸了,天天都得吵架,一个没收到钱,一个又少了钱?
今天就介绍这么多啦,希望能让你们对于线程安全和原子性有一个初步的了解,如果我有任何写得不正确不准确的地方,欢迎大家向我提出来,我非常乐意与你们学习和交流。