目录:
一.CAS详解:
二.Callable接口,ReentrantLock和Synchronized的区别:
一.CAS详解:
1.什么是CAS:
CAS: 全称Compare and swap,字⾯意思:"比较并交换",⼀个 CAS 涉及到以下操作。
CAS本质上是操作系统的一个CPU指令,操作系统把这个指令进行封装,提供了一些API,可以被C++调用,JVM又基于 C++的实现,JVM也可以使用; 但是CAS指令我们一般不直接使用,而是使用JVM和标准库封装好的
2.CAS 伪代码:
下⾯写的代码不是原子的, 真实的 CAS 是⼀个原子的硬件指令完成的. 这个伪代码只是辅助理解 CAS的工作流程.
(这整个逻辑就相当于一条CPU指令, 值科学时才进行赋值,从而实现原子性的效果 )
javaboolean CAS(address, expectValue, swapValue) { if (&address == expectedValue) { &address = swapValue; return true; } return false; }
3.CAS 的应用:
应用一:实现原自类
标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的.
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作使用代码:
javapublic class Demo { private static AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ for (int i = 0; i < 50000; i++) { //`getAndIncrement()` 会原子性地将当前值增加 1 count.getAndIncrement(); } }); Thread t2 = new Thread(()->{ for (int i = 0; i < 50000; i++) { //`getAndIncrement()` 会原子性地将当前值增加 1 count.getAndIncrement(); } }); t1.start(); t2.start(); t1.join(); t2.join(); //`get()` 获取当前值。 System.out.println(count.get()); } }
AtomicInteger 类CAS伪代码理解:
理解: value相当于内存,oldValue相当于寄存器
value==oldValue值就科学,直接返回oldValue,否则重新从内存拿值到寄存器重新判断值是否科学
javaclass AtomicInteger { private int value;//value理解为内存 public int getAndIncrement() { int oldValue = value; while ( CAS(value, oldValue, oldValue+1) != true) { oldValue = value;//value==oldValue值就科学,直接返回oldValue,否则重新从内存拿值到寄存器重新判断值是否科学 } return oldValue; } }
应用二: 实现自旋锁
javapublic class SpinLock { private Thread owner = null; public void lock(){ // 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就⾃旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){ } } public void unlock (){ this.owner = null; } }
理解:owner == null,锁就没有被别的线程占有,直接返回
如果owner != null,该锁就被的线程占有,这时返回false,!取反置为true,继续往循环里走,但是这个循环没有东西,会进行"忙等"快速拿到别的线程抛弃的锁,这就是自旋锁
3.CAS 的 ABA 问题:
1.什么是ABA 问题,某个线程把内存的值修改为A,另一个线程把内存的值修改为B,又修改成原来的A (站在值科学的角度,最后为A,说明没有被其他线程修改过,实则不然只是被多修改了而已)
这样在极端情况可能会导致线程安全问题
(说起极端情况:闰秒问题也是服务器开发中的一个极端问题,就是某个时间点时间往前跳了导致代码逻辑上出现错误)
在取钱的时候,多个线程反复进行存钱(+)取钱(-)就可能出现转账余额问题(略)
2.解决:
这种ABA问题原因在于,有其他线程参与进来,进行加也进行减少余额,
想要避免这种ABA问题就要使用版本号,这个版本只进行加,另一个版本只进行减(每次修改一次,版本号就加1)
二.Callable接口,ReentrantLock和Synchronized的区别:
1.Callable接口和Runnable接口一样都可以超创建线程:
但是 Callable接口需要重写call方法,并且这个方法还有泛型的返回值
需要真正执行任务还不足以,因为Callable只定义了一个带有返回类型的任务,并没有真正执行 ,还需要搭配futureTask和Thread对象使用
代码:
javapublic static void main(String[] args) throws ExecutionException, InterruptedException { /** Callable只定义了一个带有返回类型的任务,并没有真正执行 * 需要搭配futureTask和Thread对象使用 * */ Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int result = 0; for (int i = 0; i < 100; i++) { result += i; } return result; } }; FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread t1 = new Thread(futureTask); t1.start(); // get 操作就是获取到 FutureTask 的返回值. 这个返回值就来自于 Callable 的 call 方法. // get 可能会阻塞. 如果当前 线程 执行完毕, get 拿到返回结果. // 如果当前线程还没执行完毕, get 会一直阻塞. System.out.println(futureTask.get()); }
2.ReentrantLock和Synchronized的区别:
(1).Synchronized是关键字, ReentrantLock是JVM内部通过C++实现的标准库的类
(2).Synchronized是通过代码块控制加锁解锁, **ReentrantLock**控制加锁解锁 是通过Lock和unLock方法控制的**(要注意unLock方法没有被执行到)**
(3). ReentrantLock除了还提供了****tryLock方法(加锁成功返回ture,加锁失败返回false,也有带次数的版本)