25.Java锁的深度化

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
}
posted @ 2019-09-03 17:11  fly_bk  阅读(191)  评论(0编辑  收藏  举报