1.乐观锁
乐观锁是一种乐观思想,每次读取数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断在此期间别人有没有去更新这条数据。乐观锁只能防止脏读后数据的提交
并不能解决脏读。
实现方式:
- 加version字段,每一次的操作都会更新version,提交时如果version前后不相等,停止本次提交。
- CAS 算法
compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,所以也叫非阻塞同步。
CAS 算法涉及到三个操作数:- 需要读写的内存值 V
- 进行比较的值 A
- 拟写入的新值 B
- 当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个 native 原子操作)。一般情况下,这是一个自旋操作,即不断的重试。
2.悲观锁
悲观锁是就是悲观思想,遇到并发写的可能性高,每次去读数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。
- java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。
3.自旋锁
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自旋锁需要设定一个自旋等待的最大时间,因为自旋锁需要让CPU做无用功,如果一直获取不到锁,现在就会一直占用CPU做无用功。
- 当锁的竞争不激烈,使用自旋锁,因为自旋的消耗小于线程阻塞挂起再唤醒的操作的消耗。
- 当锁的竞争激烈,就不适合使用自旋锁,,因为自旋锁在获取锁前一直都是占用 CPU做无用功,导致CPU性能的浪费。
4.ReentrantLock
继承Lock接口并实现接口中定义的方法,它是一种可重入的锁,不仅能够完成synchronized能完成的工作,还能提供了许多避免多线程死锁方法。
public class ReentrantLockTest {
public static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> test(),"线程A").start();
new Thread(() -> test(),"线程B").start();
}
public static void test() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取了锁");
Thread.sleep(2000);
}
catch(Exception e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"释放了锁");
lock.unlock();
}
}
}
/**
* 执行结果:
* 线程A获取了锁
* 线程A释放了锁
* 线程B获取了锁
* 线程B释放了锁
*/
5.Synchronized
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
- 作用于方法时,锁住的是对象的实例(this);
- 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
- synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
6. 公平锁与非公平锁
- 公平锁
加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。 - 非公平锁
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。
7.共享锁和独占锁
- 独占锁
每次只能有一个线程能持有锁。
独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。 - 共享锁
共享锁则允许多个线程同时获取锁,并发访问共享资源。
乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
8.同步锁与死锁
-
同步锁
在同一时间内只允许一个线程访问共享数据。 Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。 -
死锁
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
线程1持有锁A,线程2持有锁B,线程1想获取锁B,线程2想获取锁A,循环等待。发生死锁的条件:
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。