Java——cas+定时器(Timer)

文章目录

  • CAS
    • 使用场景
    • ABA问题
  • 定时器(Timer)


CAS

在Java中,CAS代表Compare and Swap(比较并交换),是一种用于实现多线程并发操作的原子操作。

比如,拿寄存器A中的值和内存value的值进行比较,如果这两个值不相同,则什么操作都不做。但如果寄存器中A的值和内存中value的值相同,怎么寄存器B中的值和内存中value的值进行交换。这里并不关心交换之后B的值,更关心的是交换之后value的值,此处的交换相当于是把B赋值给了value。注意,这一系列操作是由CPU的一条指令完成的,这个操作是一个原子操作,也就是一个线程安全的操作,效率也非常高

使用场景

自增操作

CAS的使用操作的使用场景,典型的就是多线程环境下对变量自增。

下面这个是一个伪代码:

class MyAtomicInteger {
    private int value;
    /**
    这个方法相当于对变量自增,也就是++操作
    */
    public int getAndIncrement() {
        // 假设oldValue是是寄存器A的值
        int oldValue = value;
        //CAS(内存中的值,寄存器A中的值,寄存器B中的值)
        while (CAS(value,oldValue,oldValue+1) != true) {
            oldValue = value;
        }
        return oldValue;
    }
}
  • getAndIncrement方法相当于是自增操作
  • oldValue相当于是寄存器A,把内存中value的值读取到寄存器中
  • oldValue+1相当于另外一个寄存器B的中的值
  • CAS的这个函数操作,是用来比较看value这个内存中的值,是否和寄存器A的值相同,如果相同就把寄存器B的值,给设置到value中,同时CAS返回true并结束循环。如果内存中的value的值和寄存器A中的值不相同,则返回false,进入循环,在循环体里面重新读取内存中value的值到寄存器A中,再次执行CAS。
  • 每调用一次该函数就完成一次自增操作,标准库中也提供了一个AtomicInteger原子类

实现自旋锁

自选锁是纯用户态的轻量级锁,当发现锁被其它线程持有的时候,另外的线程不会挂起等待,而是会反复询问,看单前的锁是否被释放了。

伪代码实现:

class SpinLock {
    private Thread owner;
    
    public void lock() {
        // 通过CAS看当前锁是否被某个线程持有
        // 如果这个锁已经被别的线程持有,那么就自旋等待
        // 如果这个锁没有被别的线程持有,那么就把owner设为当前尝试加锁的线程
        while (!CAS(this.owner,null,Thread.currentThread())) {
        }
    }
    public void unlock() {
        this.owner = null;
    }
}
  • owner为null表示当前锁没有人持有
  • CAS中比较owner和null是否相同(是否解锁状态),如果是就要进行交换,把当前调用lock的线程的值,设置到owner里(相当于成功加锁),同时结束循环。
  • 如果owner不为null,则CAS不进行交换,返回false,又会立马进入循环判断,判断单前锁是否被其它线程持有。

自旋锁是非常消耗CPU资源的,但是它可以锁被释放时立即获得锁,适用于锁竞争比较小的场景。

ABA问题

举个例子:

我的账户余额里有1000块钱,我从ATM中取出100块,取的时候ATM卡了一下,我按了两下取钱。

这时候有两个线程,线程1和线程2都尝试进行 -100操作

线程1 获取到当前的账户余额为1000,线程2 也获取到当前的账户余额为 1000

线程1执行 -100操作,对比当前账户余额是一致的取钱成功,此时余额变成900,此时线程2就会在阻塞状态。

线程1释放锁后,线程2也尝试进行-100操作发现账户余额和当前自己读取的不一致就不会进行取钱操作。

此时相当于线程1修改成功,线程2修改失败(虽然卡了一下,出现了两个线程,但是由于CAS操作机制,保证了最终的结果是对的)

若果是极端情况:

线程1 获取到当前的账户余额为1000,线程2 也获取到当前的账户余额为 1000

线程1执行 -100操作,对比当前账户余额是一致的取钱成功,如果在取钱成功之后立马我的朋友又给我打了100块钱,接着线程2又执行 -100操作(此处不区分余额是否改变)

线程2发现此时的账户余额和刚刚读取的余额一致,于是就又扣了一次钱。

这就是CAS的ABA所引用起的问题。

CAS的ABA问题可以通过引入,版本号机制来解决。

只要有一个记录,能够记录内存中数据的变化,就可以解决ABA问题了,另外创建一个内存变量,保存账户余额的修改次数(这个版本号只增不减),或者是上次的修改时间(也是只增不减)。通过这个办法救可以解决ABA问题。

此时的修改操作,就不是把账户余额读取到寄存器A中了,比较的时候也不是比较账户余额,而是比较版本号/上次修改时间。

定时器(Timer)

定时器是开发中非常重要的一个组件,用来定时执行一些指定任务。在Java标准库中提供了一个类Timer来执行定时任务,该类有一个核心方法schedule来添加任务。

public static void main(String[] args) {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("5秒后执行的定时任务");
        }
    },5000);
}

模拟实现Timer类:

  • 需要一个带阻塞功能的优先队列(时间早的任务先执行)
  • 添加一个内部类来描述一个任务
  • 启一个线程来不断扫描优先队列中的任务是否到了执行时间,到了就执行任务,没到就重新入队列并阻塞指定时间
  • 通过wait和notify来配合阻塞和添加新的任务
  • 每次添加新任务都唤醒阻塞线程,反之新入队列中的任务执行时间比阻塞的任务时间更早。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * 模拟实现定时器Timer
 */
public class MyTimer {
    private BlockingQueue<MyTimerTask> tasks;
    private Object lock;
    /**
     * 这个类用来描述一个定时器任务
     */
    static class MyTimerTask implements Comparable<MyTimerTask> {
        private Runnable task;
        private long time;

        /**
         *
         * @param task 任务
         * @param time 多长时间之后执行该任务
         */
        public MyTimerTask(Runnable task, long time) {
            this.task = task;
            this.time = time+System.currentTimeMillis();
        }

        @Override
        public int compareTo(MyTimerTask o) {
            return (int)(this.time-o.time);
        }
    }

    public MyTimer() {
        this.tasks = new PriorityBlockingQueue<>();
        this.lock = new Object();
        // 起一个线程不断扫优先队列中的任务
        Thread t = new Thread(()->{
           while (true) {
               try {
                   MyTimerTask timerTask = this.tasks.take();
                   synchronized (lock) {
                       long time = System.currentTimeMillis();
                       if (time >= timerTask.time) {
                           // 时间到了就执行任务
                           timerTask.task.run();
                       } else {
                           // 没有到之间就重新入队列,并阻塞一会
                           this.tasks.put(timerTask);
                           lock.wait(timerTask.time-time);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   // 防止InterruptedException异常
                   break;
               }
           }
        });

        t.start();
    }

    public void schedule(Runnable task, long time) {
        MyTimerTask timerTask = new MyTimerTask(task,time);
        try {
            this.tasks.put(timerTask);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            // 每次添加任务后唤醒线程
            lock.notify();
        }
    }
}

你可能感兴趣的:(多线程,java,多线程)