• 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
    }
    
  • 相关阅读:
    20161203
    20161201
    20161128课堂笔记
    数组排序 (选择排序、冒泡排序、插入排序、希尔排序)
    编一个多用户登陆程序
    20161115课堂笔记
    20161114课堂笔记
    20161111课堂笔记
    面试常见问题
    java 基础第一周
  • 原文地址:https://www.cnblogs.com/fly-book/p/11454217.html
Copyright © 2020-2023  润新知