java的原子性和锁

296 阅读2分钟

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

原子性:

所谓的原子性 是指 在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

public class MyAtomThread implements Runnable{

    private  int count = 0; // 数量
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            count ++;
            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}
public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();
        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}

... 已经送了9997个冰淇淋 已经送了9998个冰淇淋 已经送了9999个冰淇淋

打印结果可能是少送了一个。

count++; 包含三步 出现了问题,CPU 的执行权被其他线程抢走

  1. 从共享数据中读取数据到本线程中。
  2. 修改本线程栈中变量副本的值
  3. 会把本线程栈中变量副本的值赋值给共享数据。

原子性:

count++ 不是一个原子性操作,也就是说他在执行的过程中,有可能被其他线程打断操作。

public class MyAtomThread implements Runnable{

    private volatile  int count = 0; // 数量
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            count ++;
            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}

用锁可以解决此问题

JMM:

volatile关键字: 只能保证线程每次在使用共享数据的时候是最新值,但是不能保证原子性。

public class MyAtomThread implements Runnable{

    private volatile  int count = 0; // 数量
    private Object lock = new Object();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (lock){
                count ++;
                System.out.println("已经送了" + count + "个冰淇淋");
            }
        }
    }
}

原子类 AtomicInteger

public AtomicInteger(): 初始化一个默认值为0的原子型的Integer

public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer

public class MyAtomThread implements Runnable{

//    private volatile  int count = 0; // 数量
//    private Object lock = new Object();
    AtomicInteger ac = new AtomicInteger(0);
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
           int count = ac.incrementAndGet();
           System.out.println("已经送了" + count + "个冰淇淋");        
        }
    }
}

synchronized 和 CAS 的区别

相同点:在多线程情况下,都可以保证共享数据的安全性

不同点:

synchronized总是从最坏的角度触发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。

如果别人修改过,那么我再次获取现在最新的值。 如果别人没有修改过,那么我现在直接修改共享数据的值。(乐观锁)