目录
CAS 自旋锁 AtomicInteger
AQS Lock底层
ReentrantLock底层原理
彩蛋,回顾Stream流
CAS:Compare and Swap 比较并交换的。CAS操作有3个基本参数:内存地址A,旧值B,新值C。它的作用是将指定内存地址A的内容与所给的旧值B相比,如果相等,则将其内容替换为指令中提供的新值C;如果不等,则更新失败。类似于修改登陆密码的过程。
CAS是解决多线程并发安全问题的一种乐观锁算法,保证原子性。因为它在对共享变量更新之前,会先比较当前值是否与更新前的值一致,如果一致则更新,如果不一致则循环执行(称为自旋锁),直到当前值与更新前的值一致为止,才执行更新。
JMM中更新内存变量,就使用了自旋锁
Unsafe 类是CAS的核心类,提供硬件级别的原子操作(目前所有CPU基本都支持硬件级别的CAS操作)。
// 对象、对象的属性地址偏移量(内存地址)、预期值(旧值)、修改值(新值)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public class CasDemo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//CAS : campareAndSwapInt(对象,偏移量,旧值,新值)
//AtomicInteger 具有原子性操作的类
AtomicInteger i = new AtomicInteger(1);
System.out.println("第一次更新" + i.compareAndSet(1,200)); //200
System.out.println("第一次更新后的值" + i.get()); //true
System.out.println("第二次更新" + i.compareAndSet(1,300)); //200
System.out.println("第二次更新后的值" + i.get()); //false
System.out.println("第三次更新" + i.compareAndSet(200,300)); //300
System.out.println("第三次更新后的值" + i.get()); //true
}
}
验证原子性:
class DataOne{
//private volatile Integer number = 0;
private AtomicInteger number = new AtomicInteger(0);
//验证volatile不具备原子性,synchronized具备原子性
public synchronized Integer incr(){
//return ++number;
return number.incrementAndGet();
}
}
public class VolatileAtomicDemo {
public static void main(String[] args) {
DataOne dataOne = new DataOne();
//1000个线程调用+1, 方法:正确结果应该是1000
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(dataOne.incr());
}).start();
}
}
}
为什么JUC下atomic类 是原子性的
缺点:
开销大:在并发量比较高的情况下,如果反复尝试更新某个变量,却又一直更新不成功,会给CPU带来较大的压力
ABA问题:当变量从A修改为B再修改回A时,变量值等于期望值A,但是无法判断是否修改,CAS操作在ABA修改后依然成功。
不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。(要想整个代码块的原子性,加锁)
AQS:AbstractQueuedSynchronizer 抽象队列同步器。它是实现同步器的基础组件(框架),JUC 中Lock的实现 (ReentrantLock、ReentrantReadWriteLock) 以及一些并发工具类
(CountDownLatch、CyclicBarrier、Semaphore) 就是通过AQS来实现的。具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。
但是,StampLock不是基于AQS实现的 (Java8推出,ReentrantReadWriteLock 升级版,读多写少时可避免饥饿问题)。
AQS维护了一个volatile语义的共享资源变量state(支持多线程下的可见性),和一个FIFO线程等待队列(first-in-first-out)(多线程竞争state资源被阻塞时,会进入此队列)。
基于AQS实现锁的思路
AQS将大部分的同步逻辑均已经实现好,继承的自定义同步器只需要实现state的获取(acquire)和释放(release)的逻辑代码就可以,主要包括下面方法:
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
也就是说:
通过AQS可以实现独占锁 (一个线程可获取的锁,ReentrantLock 可重入锁),也可以实现共享锁 (多个线程可获取的锁Semaphore(信号量)/CountDownLatch(倒计时线程控制)等)
以ReetrantLock为例, 说明AQS在锁底层的应用。
Lock锁是用AQS来实现的,资源抢占上锁用到了CAS。
在ReentrantLock类中包含了3个AQS的实现类:
1. 抽象类Sync
2. 非公平锁实现类NonfaireSync
3. 公平锁实现类FairSync
FairSync 底层实现 (AQS)
上锁:
上锁具体步骤:
当等待队列只有一个线程时,直接获取到锁
如果队列不止一个线程,并且下一个线程就是当前申请锁的线程(可重入锁),则获取锁
补充:各种锁,synchronized:偏向锁(偏向第一个线程,效率最高) ---> 如果有线程竞争升级为轻量级锁(自旋锁) ---> 自旋10次升级为重量级锁(悲观锁
1. 四大函数式接口
2. Stream流
数据源 + 计算 + 结束操作
找出符合条件的数据
* 偶数ID
* 年龄大于24
* 用户名转大写
* 用户名字倒排序
* 只输出一个
* 用户名字
* */
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(11, "a", 23);
User u2 = new User(12, "b", 24);
User u3 = new User(13, "c", 22);
User u4 = new User(14, "d", 28);
User u5 = new User(16, "e", 26);
List list = Arrays.asList(u1, u2, u3, u4 ,u5);
list.stream().filter(p->{return p.getId() % 2 == 0;})
.filter(p->{return p.getAge() > 24;})
.map(f->{return f.getUserName().toUpperCase();})
.sorted((o1,o2)->{return o2.compareTo(o1);}) //复制小括号 写死右箭头 落地大括号
.limit(1).forEach(System.out::println); //E
}
}
关于面试,你还有什么要了解的吗
1. 公司技术部门的团队架构,前端、java、测试、运维团队人员。部门有的高手吗
2. 如果可以进入贵公司,对于试用期的员工,相关的技术、业务考核要求是什么 (底线)
3. 每年的新人一定很多,一定有很多优秀的,他的技术能力能达到什么水平 (上线)