Java锁的深度化
悲观锁、乐观锁、排他锁
场景
当多个请求同时操作数据库时,首先将订单状态改为已支付,在金额加上200,在同时并发场景查询条件下,会造成重复通知。 SQL: Update
悲观锁与乐观锁
- 悲观锁:
悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
- 乐观锁:
乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制
重入锁
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利。 重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁
public class SynchronizedTest implements Runnable {
public synchronized void get(){
System.out.println(Thread.currentThread().getName()+" get()");
set();
}
private synchronized void set() {
System.out.println(Thread.currentThread().getName()+" set()");
}
@Override
public void run() {
get();
}
public static void main(String[] args){
SynchronizedTest test = new SynchronizedTest();
new Thread(test).start();
new Thread(test).start();
new Thread(test).start();
new Thread(test).start();
}
//Thread-0 get()
//Thread-0 set()
//Thread-3 get()
//Thread-3 set()
//Thread-2 get()
//Thread-2 set()
//Thread-1 get()
//Thread-1 set()
}
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo extends Thread{
ReentrantLock lock = new ReentrantLock();
public void get(){
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
private void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args){
ReentrantLockDemo demo = new ReentrantLockDemo();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
//10
//10
//11
//11
//12
//12
}
读写锁
相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。 在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源, 就不应该再有其它线程对该资源进行读或写(读-读能共存,读-写不能共存,写-写不能共存)。
原子类
java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程
原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
为什么会有原子类
- CAS:Compare and Swap,即比较再交换。
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
- 如果同一个变量要被多个线程访问,则可以使用该包中的类(原子类)
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
常用原子类
Java中的原子操作类大致可以分为4类:原子更新基本类型、原子更新数组类型、原子更新引用类型、原子更新属性类型。这些原子类中都是用了无锁的概念,有的地方直接使用CAS操作的线程安全的类型。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo implements Runnable{
private static Integer count = 1;
private static AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
while (true){
// int count = getCount();
int count = getCountAtomic();
System.out.println(count);
if (count>=50){
break;
}
}
}
private Integer getCountAtomic() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return atomicInteger.incrementAndGet();
}
public synchronized Integer getCount(){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
}
public static void main(String[] args){
AtomicIntegerDemo demo = new AtomicIntegerDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
}
}
CAS(乐观锁算法)无锁机制
- 与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
- 无锁的好处:
- 在高并发的情况下,它比有锁的程序拥有更好的性能
- 它天生就是死锁免疫的
CAS缺点
CAS存在一个很明显的问题,即ABA问题。
- 问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?
如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。
AtomicReference
- AtomicReference用以支持对象的原子操作:AtomicReference
可以封装引用一个V实例 - public final boolean compareAndSet(V expect, V update) ,可以支持并发访问,set的时候进行对比判断,如果当前值和操作之前一样则返回false,否则表示数据没有变化
import java.util.concurrent.atomic.AtomicReference;
/**
* CAS算法:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。
* 仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。
* 最后,CAS返回当前V的真实值
*/
public class SpinLockDemo implements Runnable {
static int sum;
private SpinLock lock;
public SpinLockDemo(SpinLock lock) {
this.lock = lock;
}
@Override
public void run() {
this.lock.lock();
sum++;
this.lock.unlock();
}
public static void main(String[] args) throws InterruptedException{
SpinLock spinLock = new SpinLock();
for (int i = 0; i < 100; i++) {
SpinLockDemo demo = new SpinLockDemo(spinLock);
Thread thread = new Thread(demo);
thread.start();
}
Thread.currentThread().sleep(1000);
System.out.println(sum);//100
}
}
//自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区
class SpinLock{
//Java中的原子操作(CAS)
//持有自旋锁的线程对象
AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
//lock函数将thread设置为当前线程,并且预测原来的值为null
//当有第二个线程调用lock操作时由于thread的值不为空,导致循环
//一直被执行,直至第一个线程调用unclock函数将sign设置为null,第二个线程才能进入临界区
while (!sign.compareAndSet(null,thread));
}
public void unlock(){
//unlock将sign的值设置为null,并且预测值为当前线程
Thread thread = Thread.currentThread();
sign.compareAndSet(thread,null);
}
}
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
private static AtomicReference<Integer> ar = new AtomicReference<>(0);
public static void test() throws InterruptedException{
int count = 3;
final int c = 3;
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(()->{
for (int j = 0; j < c; j++) {
while (true){
Integer temp = ar.get();
System.out.println("temp="+temp);
//public final boolean compareAndSet(V expect, V update)
if (ar.compareAndSet(temp,temp+1)){
break;
}
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println(ar.get());
}
public static void main(String[] args) throws InterruptedException{
test();
//temp=0
//temp=0
//temp=1
//temp=2
//temp=1
//temp=3
//temp=3
//temp=4
//temp=4
//temp=5
//temp=5
//temp=6
//temp=7
//temp=8
//9
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 原子量实现的计数器
*/
public class AtomicCounter {
private AtomicInteger value = new AtomicInteger();
public int getValue(){
return value.get();
}
//+1
public int increase(){
// return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
return value.incrementAndGet();
}
//+delta
public int increase(int delta){
// return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
return value.addAndGet(delta);
}
//-1
public int decrease(){
// return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
return value.decrementAndGet();
}
//-delta
public int decrease(int delta){
// return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
return value.addAndGet(-delta);
}
public static void main(String[] args){
final AtomicCounter counter = new AtomicCounter();
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(counter.increase(2));
});
}
threadPool.shutdown();
}
}
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* 原子量实现的银行取款
*/
public class AtomicAccount {
private AtomicLong balance;
public AtomicAccount(long money) {
balance = new AtomicLong(money);
System.out.println("Total Money:"+balance);
}
//存钱
public void deposit(long money){
balance.addAndGet(money);
}
//取钱
public void withdraw(long money){
for (;;){
long oldValue = balance.get();
if (oldValue<money){
System.out.println(Thread.currentThread().getName()+" 余额不足!"+" 余额:"+balance);
break;
}
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (balance.compareAndSet(oldValue,oldValue-money)){
System.out.println(Thread.currentThread().getName()+" 取款:"+money + " 余额:"+balance);
break;
}
System.out.println(Thread.currentThread().getName() + " 遇到并发,再次尝试取款!");
}
}
public static void main(String[] args){
final AtomicAccount account = new AtomicAccount(1000);
ExecutorService threadPool = Executors.newCachedThreadPool();
int i = 0;
while (i++<13){
threadPool.execute(()->{
account.withdraw(100);
});
}
threadPool.shutdown();
}
//Total Money:1000
//pool-1-thread-13 取款:100 余额:900
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-5 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-3 遇到并发,再次尝试取款!
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-4 遇到并发,再次尝试取款!
//pool-1-thread-8 遇到并发,再次尝试取款!
//pool-1-thread-9 遇到并发,再次尝试取款!
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-8 取款:100 余额:800
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-5 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-5 取款:100 余额:700
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-4 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-4 取款:100 余额:600
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-3 遇到并发,再次尝试取款!
//pool-1-thread-3 取款:100 余额:500
//pool-1-thread-9 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-9 取款:100 余额:400
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-11 取款:100 余额:300
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-7 取款:100 余额:200
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-2 取款:100 余额:100
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-12 取款:100 余额:0
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-6 余额不足! 余额:0
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-10 余额不足! 余额:0
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-1 余额不足! 余额:0
}