提示:以下是本篇文章正文内容,Java系列学习将会持续更新
1. 独占锁和共享锁
共享锁:Share Lock (S锁), ‘读’操作之间不互斥,‘写’操作互斥,‘读+写’操作互斥。
独占锁:exclusive Lock (X锁), 所有操作之间都互斥。
如果某个场景下“一写多读”,使用读写锁效率就很高。
因为只进行读数据,多个线程同时读取一个数据,不会造成线程不安全的情况。只有修改同一个数据才会线程不安全
Java中的读写锁:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
// readLock之间不互斥,readLock和writeLock之间互斥,writeLock之间互斥
readLock.lock(); // 主线程加到读锁
Thread t = new Thread() {
@Override
public void run() {
readLock.lock(); // 主线程已经抢到读锁,子线程依然可以抢到锁
//writeLock.lock(); // 写锁就不行了
System.out.println("子线程也可以加读锁成功");
}
};
t.start();
2. 可重入锁和不可重入锁
可重入锁:一个线程已经持有lock1锁的情况下,允许再次获取到lock1同一把锁。
synchronized
、ReentrantLock
都是可重入锁。
public static void main(String[] args) {
Object lock = new Object();
synchronized (lock) { // main 线程已经对 lock 加锁了
synchronized (lock) { // main 线程再次对 lock 请求锁(处于已经锁上状态)
System.out.println("这里打印了就说明了 sync 锁是可重入锁");
}
}
}
3. 公平锁和非公平锁
公平锁:有多个线程按照申请锁的顺序来获取锁,就是说,如果一个线程组里面,能够保证每个线程都能拿到锁。
例如:ReentrantLock
可以通过传入fair = true / false
来控制是否公平, 默认是不公平。(fair = true,使用的同步队列FIFO)
非公平锁:获取锁的方式是随机的,保证不了每个线程都能拿到锁,会存在有的线程饿死,一直拿不到锁,例如:synchronized
总结:非公平锁性能高于公平锁,更能重复利用CPU的时间
4. 乐观锁和悲观锁
不是真正意义上的锁,而是在多线程的情况下,评估并发的情况来设计锁:
乐观锁: 多个线程获取同一个共享资源时,乐观地认为它们不会修改数据,所以不去上锁。或者只有极个别线程去修改数据,可以采用轻量级锁方式的进行并发控制。cas是乐观锁,通过原子性来保证数据的同步
。
悲观锁: 不管多个线程是否会频繁地修改同一个共享资源,必须使用互斥的方式(锁lock)来进行并发控制。如synchronized
、ReentrantLock
总结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁高
什么是 CAS?
CAS: 全称Compare and swap,字面意思:”
比较并交换
“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
1. 比较 A 与 V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V。(交换)
3. 返回操作是否成功
原理:反复循环(操作 + 判断:实际值==预期值 ?结束循环:继续操作+判断)
CAS的应用:
1. 实现java.util.concurrent.atomic 包下的原子类
AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();
2. 实现了自旋锁
5. 互斥锁和自旋锁
从实现原理上来讲:
Mutex(互斥锁)属于
sleep-waiting
类型的锁。例如在一个单核的机器上有两个线程(线程A和线程B)。假设线程A想要去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,此时会将线程A置于等待队列中,等待线程B释放锁来唤醒线程A。
而Spin lock(自旋锁)适用于多核计算机下,它属于busy-waiting
类型的锁,如果线程A请求不到锁,不会引起线程调度(让出CPU),而是一直在那里循环看是否该自旋锁的持有者已经释放了锁,并不停的进行锁请求,直到得到这个锁为止。
自旋锁的使用:
①我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP(多核)的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。
②“自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠(线程调度),所以自旋锁的效率远高于互斥锁。
③自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。所以自旋锁比较适用于锁持有者持有锁时间比较短的情况。
④试图递归地获得自旋锁必然会引起死锁: 递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。
总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java多线程的学习,认识了不同的锁策略,以及这些策略的应用场景。之后的学习内容将持续更新!!!