ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
* ## **重入锁** 重入锁也称递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但却不会受到影响,ReentrantLock 和synchronized都是可重入锁。 ``` class TaskThread implements Runnable { public synchronized void set() { System.out.println(Thread.currentThread().getName() + ":set"); } public synchronized void get() { System.out.println(Thread.currentThread().getName() + ":get"); set(); } @Override public void run() { get(); } } class test { public static void main(String[] args) { TaskThread taskThread = new TaskThread(); Thread t1 = new Thread(taskThread); Thread t2 = new Thread(taskThread); t1.start(); t2.start(); } } ``` 执行结果 ``` Thread-0:get Thread-0:set Thread-1:get Thread-1:set ``` get方法中调用set方法,2个方法都加入了synchronized锁,但是并没有产生死锁的现象。说明set方法能获取到上层的get的锁。 * ## **读写锁** ReentrantLock和synchronized都为排它锁,在同一时刻只允许一个线程访问锁资源,而读写锁在同一时刻可以允许多个读线程访问,在写线程访问的时候其他的读线程和写线程都会被阻塞,读写锁维护一对锁(读锁和写锁),通过锁的分离,使得并发性提高,适用于读多写少的应用场景。 ``` ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); // 读锁 Lock r = rwl.readLock(); // 写锁 Lock w = rwl.writeLock(); ``` * ## **悲观锁与乐观锁** ### 乐观锁 不会上锁的一种锁,通常采用添加version版本字段的方式,每次修改前都会对比版本号,修改后version+1 ### 悲观锁 每次读取数据都认为其他线程会修改数据,所以都会加锁,synchronized也算是悲观锁,还有数据库的行锁,读锁,写锁也都是悲观锁。 * ## **原子类** 线程安全的3大特性之一就是原子性,要想保证线程原子性,我们通常会采用加入synchronized的方式。但是synchronized锁会产生阻塞,必定会影响程序性能。对于一些相对标准化的共享变量的多线程操作,比如i++等,java并发包提供了一种更加有效率的方式.。 ``` private AtomicInteger count = new AtomicInteger(); ... count.incrementAndGet() ... ``` 通过创建AtomicInteger对象,调用incrementAndGet方法就能实现线程安全的非阻塞式的原子操作,java.util.concurrent.atomic原子操作工具包中还提供了很多其他类型的操作做。 ``` AtomicInteger AtomicBoolean AtomicLong AtomicReference ``` 这些原子类中都是用了无锁的概念,大多使用CAS无锁机制来实现底层原理。 * ## **CAS无锁机制** CAS是英文单词**Compare And Swap**的缩写,翻译过来就是比较并替换。 CAS的三个核心参数CAS(V,E,N): ``` 1. 内存地址V:共享变量的内存地址,jmm模型中可以理解为共享变量存放在主内存中值。 2. 预期值E:jmm模型中可以理解为保存在线程本地缓存中的值。 3. 新值N:线程经过运算后,即将要写入到主内存中的值。 ``` CAS机制中,更新一个变量的时候,只有当变量的预期值E和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为N。举个例子: ``` 1.在内存地址V当中,存储着值为5的变量。 2.此时线程1想要把变量的值增加1。对线程1来说,预期值E=5,要修改的新值N=6。 3.但是,在线程1要提交更新之前,另一个线程2把内存地址V中的变量值率先更新成了6。 4.此时,线程1开始提交更新,首先进行E和地址V的实际值比较(Compare),发现E不等于V的实际值,提交失败。 5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,E=6,N=7。 6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现E和地址V的实际值是相等的。 7.线程1进行替换,把地址V的值替换为N,也就是7。 ``` 知道了CAS的执行机制,再来分析`AtomicInteger `的源码 ``` public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // 用来实现CAS机制的实例 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 共享变量在主内存中偏移量,可以理解是V值 private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; ... // 自增的方法 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } ... } ``` 再来看`getAndAddInt`方法的内部实现 ``` // 原生方法实现CAS算法 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); // 原生方法获取期望值E public native int getIntVolatile(Object var1, long var2); public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { // 调用原生方法获取期望值E var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } ``` 代码可以理解为 ``` do { 获取期望值E } while(!CAS(内存地址V, 期望值E, 新值N)); return 期望值E; ``` CAS机制的缺点 问题:如果变量V初次读取的时候是5,并且在准备赋值的时候检查到它仍然是5,那能说明它的值没有被其他线程修改过了吗? 如果在这段期间曾经被改成6,然后又改回5,那CAS操作就会误认为它从来没有被修改过。但是这对当前线程的执行会有影响吗???? * ## **Synchronized实现原理** 未完待续,貌似挺复杂 * ## **Lock实现原理与AQS** 未完待续,貌似挺复杂 * ## **自旋锁** 自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里,会持续占用CPU资源,自旋锁适用于锁使用者保持锁时间比较短的情况。CAS属于自旋锁模式 * ## **Disruptor并发框架** 能够在一个线程里每秒处理6百万订单的开源并发框架。附上链接 [http://ifeve.com/disruptor/](http://ifeve.com/disruptor/)