在java中存在这样一种现象,当两个线程同时对一个数据进行操作的时候可能出现错误的现象,那么有没有办法做到让多个线程操作同一个数据的时候不会紊乱呢
看现象:
public class Test {
private static Person person;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
System.out.println("Person is " + person.toString());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
//让主线程等待这个两个线程结束后再结束
t1.join();
t2.join();
System.out.println("Now Person is " + person.toString());
}
static class Task1 implements Runnable{
@Override
public void run() {
person.setAge(person.getAge()+1);
person.setName("Tom1");
System.out.println("Thread1 Values "+ person.toString());
}
}
static class Task2 implements Runnable{
@Override
public void run() {
person.setAge(person.getAge()+1);
person.setName("tom2");
System.out.println("Thread2 Values "+ person.toString());
}
}
}
上面的代码是创建两个线程同时开启对同一个对象进行更改数据,当然80%的情况下是正确的结果,但是也有一部分情况下出现这样的结果
这种情况出现的概率还是比较大的,作为一名合格的程序员,怎么能允许我的程序里面出现这样的低级错误呢。
原子性应用
这个概念给我的理解就是直接操作原始数据
给大家说一个简单的例子,我们知道计算机的cpu运算的速度超级快,然是内存IO的速度相对cpu而言就超级慢,如果是这样的话,我们的cpu资源就大大的浪费了,于是就提出的高速缓存这个概念是一种介于内存和cpu之间的存储方式,它的原理是在提前在高速缓存里面操作然后计算完之后再拷贝的内存里面去。这样一来就很好的利用了cpu的性能。这个对于单线程当然一点问题也没有。“`
但是对于多线程的时候问题就来了,两个线程同时操作一个数据,按道理应该是两次结果的叠加,但是有了高速缓存之后会存在后一种结果把上一个结果直接覆盖了就像上面的例子,这时候我们就需要原子性操作,直接对原始数据操作
```java
public class Test {
private static Person person;
private static AtomicReference<Person> aotmic;
private static int stamp;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
aotmic = new AtomicReference<>(person);
System.out.println("Person is " + aotmic.get().toString());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
//让主线程等待这个两个线程结束后再结束
t1.join();
t2.join();
System.out.println("Now Person is " + aotmic.get().toString());
}
/**
* 这个我们使用了AtomicReference比较交换并发策略,这个策略,会有三个变量,
* V需要改变的变量
* E 期望的变量
* N 新的变量值
* 当期望的变量值和新的变量值相同的时候才会把需要改变的变量赋值为N
* 当期望的变量值和需要改变的变量的值不相同的时候,程序不会进行赋值改变,但是不会报错
* 总而言之,这种方法避免了覆盖,但是线程之间还是无法避免对同一个数据的操作
* 只要任何一个线程抢占成功,其他线程均修改失败
*/
static class Task1 implements Runnable{
@Override
public void run() {
aotmic.get().setAge(aotmic.get().getAge()+1);
aotmic.get().setName("Tom1");
System.out.println("Thread1 Values "+ aotmic.get().toString());
}
}
static class Task2 implements Runnable{
@Override
public void run() {
aotmic.get().setAge(aotmic.get().getAge()+1);
aotmic.get().setName("tom2");
System.out.println("Thread2 Values "+ aotmic.get().toString());
}
}
}
···
> 以上的这种操作有效的避免了两个甚至多个线程同时修改一个文件的问题,因为它操作的是原子性变量,原子性的变量在操作的时候是无法中断的,但是当和这个值被反复修改后例如 原来为 1中间一系列加减最后又是1,这样的情况表面上没什么问题,但是有些情况,例如给卡内小于 10000元的充值100元,不能我消费了之后小于10000还要冲值吧,这里就涉及到AtomicStampedReference,这是带时间错的是得原子变量操作
<div class="se-preview-section-delimiter"></div>
```java
public class Test {
private static Person person;
private static AtomicStampedReference<Person> aotmic;
private static int stamp;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
aotmic = new AtomicStampedReference<>(person,0);
stamp = aotmic.getStamp();
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
//让主线程等待这个两个线程结束后再结束
t1.join();
t2.join();
System.out.println("Now Person is " + aotmic.getReference().toString());
}
static class Task1 implements Runnable{
@Override
public void run() {
Person refence = aotmic.getReference();
if (aotmic.compareAndSet(aotmic.getReference(),refence,stamp,stamp+1))
{
refence.setAge(aotmic.getReference().getAge()+1);
refence.setName("Tom1");
System.out.println("Thread1 Values "+ aotmic.getReference().toString());
}
}
}
static class Task2 implements Runnable{
@Override
public void run() {
Person refence = aotmic.getReference();
if (aotmic.compareAndSet(aotmic.getReference(),refence,stamp,stamp+1)){
refence.setAge(aotmic.getReference().getAge()+1);
refence.setName("Tom2");
System.out.println("Thread2 Values "+ aotmic.getReference().toString());
}
}
}
}
简单的介绍下上面的方法思想,经过我们多天的面壁思过得出,当多个线程同时操作一个类或者变量的时候,给它设置了时间操作参数,当现在的引用的时间参数和期望的时间参数一致的时候就执行if条件里面的内容,同时时间参数会增加一个系数