一、简介
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
- 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
二、悲观锁的实现
对象锁:synchronized
锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念。
synchronized是对类的当前实例(当前对象)进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。
import java.util.concurrent.TimeUnit;
public class Test1 {
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
Test1 test = new Test1();
new Thread(test::start, "线程A").start();
new Thread(test::end, "线程B").start();
}
}
执行结果:
线程A,start 线程B,end
类锁:static synchronized
该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
StaticTest a = new StaticTest();
StaticTest b = new StaticTest();
new Thread(a::start, "线程A").start();
new Thread(b::end, "线程c").start();
}
执行结果:
线程A,end 线程c,start
更改start、end方法为static后,执行结果:
线程c,start 线程A,end
synchronized+static synchronized
public class StaticTest {
public synchronized void start() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",start");
}
public static synchronized void end() {
System.out.println(Thread.currentThread().getName() + ",end");
}
public static void main(String args[]) {
StaticTest a = new StaticTest();
StaticTest b = new StaticTest();
new Thread(() -> a.start(), "线程c").start();
new Thread(() -> a.end(), "线程A").start();
}
}
执行结果:
线程A,end 线程c,start
synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。
三、乐观锁的实现
CAS算法
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
这就容易引起ABA问题
一个线程X1从内存位置V中取出A,这时候另一个线程Y1也从内存中取出A,并且Y1进行了一些操作变成了B,然后Y1又将V位置的数据变成A,这时候线程X1进行CAS操作发现内存中仍然是A,然后X1操作成功。尽管线程X1的CAS操作成功,但是不代表这个过程就是没有问题的。
解决方案CAS类似于乐观锁,即每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。因此解决方案也可以跟乐观锁一样:使用版本号机制,如手动增加版本号字段