概念
CAS (compareAndSet(最新) 或compareAndSwap JDK不同版本名称不同)
当且仅当原值==期望值时,才更新为新值
具体表现为:
当一个线程需要修改某个值时,会将原值(即当前线程中该值的副本)跟主内存中的这个值进行比较,如果相等,就更新为新值.
例如 count++; 当前线程中count=2(原值); 主内存中的count=2(即期望值);此时count 就执行++操作,更新为count=3(新值)
如果此时当前线程中count=2(原值); 主内存中的count=3(即期望值);就不做任何操作.因为count=3就证明该值被其它线程改过
由此实现cas操作
为什么这么做:
是一种锁优化的形式,避免上重量级锁.
安全性思考:
- CAS操作本身会不会有线程安全问题?
答:是CPU指令级的操作 不能被打断 所以不存在线程安全问题 - 如何解决ABA问题?
答:加版本号控制
在JUC包下,凡是以Atomic开头的类都是用CAS操作保证线程安全,所以在实际开发中,例如遇到多线程递增计数需求时,可使用AtomicXXX等类提高多线程执行效率
ReentrantLock(可重入锁)与synchronized
其实跟sychronized 作用差不多,相当于是JDK1.5之后的sychronized 在JDK中的实现
本质上的区别在于sychronized 是JVM中的实现(不同的JVM,sychronized 的实现可以不同),ReentrantLock是JDK中的实现
在Java中,实现同步操作可以使用sychronized 或者Lock.lock(),Lock.unlock()两种方式锁住需要同步的代码块
但是在早期JDK版本中 加锁的操作是直接通过JVM通知操作系统使用内存屏障的原理进行控制 加锁操作直接进入内核态
后来的JDK对sychronized 进行了锁升级的优化,优化后的sychronized 操作步骤如下:
- 偏向锁 即 当前线程A进入同步代码块之后,会使用Mark Word标记该线程A已进入,以便后续线程进入时进行比较,如果还是A线程再次进入就不升级
- 自旋锁 即 当B线程进入时,此时偏向锁就会升级为自选锁,在等待A线程执行结束之前,会循环10次判断A线程是否执行完,超过10,锁升级
偏向锁和自旋锁都是在JVM中完成的,都属于用户态 - 重量级锁 即 通过JVM通知操作系统进行线程调度 属于内核态
通过了解sychronized 的实现思路 对比ReentrantLock的实现
ReentrantLock就是将sychronized 的底层通过Java语言来实现 所以在实际开发使用当中 用法没有太大区别
所以使用ReentrantLock替代sychronized 是没问题的 就好比使用Lock.lock(),Lock.unlock()方式或sychronized 实现锁定同步代码块
ReentrantLock还有其它特性:
- tryLock 尝试锁,如果锁不住,可以执行哪些操作
- lockInterupptibly 打断锁
- 公平锁
ReentrantLock的实现原理:CAS
ReentrantLock与sychronized 的最大区别在于:ReentrantLock可以实现公平锁 而sychronized 是非公平的
公平与非公平锁
其实现主要依赖AQS(AbstractQueuedSynchronizer)这个类
原理:CAS 在AbstractQueuedSynchronizer维护了一个双向链表 和一个volatile修饰的state
公平锁:当新的线程进来时,进入双向链表,再进行state(本质上就是那把锁) 的竞争
非公平锁:当新的线程进来时,直接进入state 的竞争
JDK中其它使用CAS实现的锁
CountDownLatch 倒数门闩 用来等线程结束之后操作
CyclicBarrier N个线程满了 一起发车
Phaser 分段锁 遗传算法 分段栅栏 CyclicBarrier 升级版 颗粒度更细
ReadWriteLock 读写锁 即共享锁和排它锁
Semaphore 信号灯 限流 最多允许N个线程同时执行
Exchanger 交换器 线程之间交换变量值
LockSupport park()和unpark() 本质上和wait() notify() 一样
重点看下读写锁ReadWriteLock
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
private static Lock lock = new ReentrantLock();
private static int value;
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
public static void read(Lock lock) {
try {
lock.lock();
Thread.sleep(1000);
System.out.println("read over!");
//模拟读取操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void write(Lock lock, int v) {
try {
lock.lock();
Thread.sleep(1000);
value = v;
System.out.println("write over!");
//模拟写操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// Runnable readR = ()-> read(lock);
Runnable readR = ()-> read(readLock);
// Runnable writeR = ()->write(lock, new Random().nextInt());
Runnable writeR = ()->write(writeLock, new Random().nextInt());
for(int i=0; i<18; i++) new Thread(readR).start();
for(int i=0; i<2; i++) new Thread(writeR).start();
}
}
这里的readLock 读锁 是一种乐观锁 就是做读操作时 不排他 可以多线程并发去读
而writeLock 写锁 是一种悲观锁 也是排他锁
读写不同的操作使用不同类型的锁 本质上体现的是应对高并发时读写分离的思想
ReadWriteLock是Java程序中的锁, 但与数据库层面的悲观锁乐观锁思想上有相似之处