ABA问题和ABC问题

345 阅读2分钟

ABA问题

你给的A不是原来的A,在一个线程里面原来是A,然后修改为B,再修改为A,这个A的数值都是一样的,但是A的对象已经不一样了,因为已经被修改了;

最好的例子就是AtomicXxx类了;

public class Test {
    static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static final Object lock = new Object();
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                if (atomicInteger.getAndIncrement() == 1) {
                    System.out.println("ThreadA before" + atomicInteger.get());
                    atomicInteger.getAndDecrement();
                    System.out.println("ThreadA after" + atomicInteger.get());
                }
            }
        }, "ThreadA");
        Thread threadB = new Thread(() -> {
            synchronized (lock) {
                if (atomicInteger.getAndIncrement() == 1) {
                    System.out.println("ThreadB before" + atomicInteger.get());
                    atomicInteger.getAndDecrement();
                    System.out.println("ThreadB after" + atomicInteger.get());
                }
            }
        }, "ThreadB");

        threadA.start();
        threadB.start();
    }
}

\

可以看到里面,线程A结束了,线程B会继续执行呢。

现在给这个加入版本的控制,使用AtomicStampedReference

    static AtomicStampedReference<Integer> num = new AtomicStampedReference<Integer>(1,0);
public class Test {
    static AtomicInteger atomicInteger = new AtomicInteger(1);

    // 设置数值为1,版本为0
    static AtomicStampedReference<Integer> num =new AtomicStampedReference<Integer>(1,0);
    public static final Object lock = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(()->{
                    synchronized (lock) {
                        // 期望的值是1,然后设置新值为2,期待版本为0,然后设置为1
                        if (num.compareAndSet(1, 2, 0, 1)) {
                            System.out.println("ThreadA before" + atomicInteger.get());
                            // 期望的值是2,然后设置新值为1,期待版本为1,然后设置为2
                            num.compareAndSet(2, 1,1, 2);
                            System.out.println("ThreadA after" + atomicInteger.get());
                        }
                    }
                }, "ThreadA");
                Thread threadB = new Thread(()->{
                    synchronized (lock) {
                        if (num.compareAndSet(1, 2,0, 1)) {
                            System.out.println("ThreadB before" + atomicInteger.get());
                            num.compareAndSet(2, 1,1, 2);
                            System.out.println("ThreadB after" + atomicInteger.get());
                        }
                    }
                }, "ThreadB");

                threadA.start();
                threadB.start();
    }
}

在线程A中执行后数值变成原来的数值,但是这个数值的版本号收到了改变;

在线程B中就无法获取相对应的数据了,这样线程B就没法执行了。

ABC问题

线程顺序执行

  1. 使用可重入锁进行显示的锁控制
  2. 使用CountDownLatch保证第一次执行的时候的顺序,也就是后面的Thread.start()
public class ReentrantLockTest {
    // 创建一个可重入的锁
    static final ReentrantLock lock = new ReentrantLock();
    
    // 创建三个条件,其实也就是三个队列,对应的是main方法里的A.B,C线程
    static final Condition cA = lock.newCondition();
    static final Condition cB = lock.newCondition();
    static final Condition cC = lock.newCondition();
    
    // 创建两个限制条件
    static final CountDownLatch latchB = new CountDownLatch(1);
    static final CountDownLatch latchC = new CountDownLatch(1);
        
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            // 获取唯一的锁,让线程B和线程C都没办法操作
            lock.lock();
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("A");
                    if (i == 0) latchB.countDown();
                    // 唤醒B类线程。在我这也就是ThreadB
                    cB.signal();
                    // 将此线程(ThreadA)放入cA中,并且将线程状态设置为等待的状态
                    cA.await();
                }
                // 唤醒B类线程。
                cB.signal();
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 将锁释放
                lock.unlock();
            }
        }, "ThreadA");
        
        Thread threadB = new Thread(() -> {
            try {
                // 在等待线程A将latchB的size设为0呢,也就是在线程A中执行的 latchB.countDown()
                latchB.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 获取锁,让B单独执行,这个时候线程A已经处于等待状态了,而且线程C现在还在等待latchC的释放
            lock.lock();
            
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("B");
                    // 释放线程C的latchC
                    if (i == 0) latchC.countDown();
                    // 唤醒线程C
                    cC.signal();
                    // 将线程B设置为等待状态
                    cB.await();
                }
                cC.signal();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                // 解锁
                lock.unlock();
            }
            
        }, "threadB");
        
        Thread threadC = new Thread(() -> {
            try {
                
                // 在等待线程B将latchC的size设为0呢,也就是在线程A中执行的 latchC.countDown()
                latchC.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                // 获取锁,让C单独执行,这个时候线程A已经处于等待状态了,而且线程B都处于等待的状态了
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    System.out.println("C");
                    // 唤醒线程A
                    cA.signal();
                    // 线程C处于等待的状态
                    cC.await();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "threadC");
        
        threadA.start();
        threadB.start();
        threadC.start();
    }
}