zoukankan      html  css  js  c++  java
  • 锁机制

    1.锁是干什么用的
      锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误

    2.重入锁
      重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限
      synchronized和ReentrantLock就是重入锁对应的实现
      synchronized重量级的锁
      ReentrantLock轻量级的锁 lock()代表加入锁 unlock()代表释放锁

      不可重入锁:说明当没有释放该锁时。其他线程获取该锁会进行等待

    public class MyLock {
        //标识锁是否可用  如果值为 true代表有线程正在使用该 锁 ,如果为false代表没有人使用锁
        private boolean isLocked=false;
        //获取锁:加锁
        public synchronized void lock() throws InterruptedException {
            //判断当前该锁是否正在使用
            while (isLocked){
                wait();
            }
            //当前没有人使用情况 下就占用该锁
            isLocked=true;
        }
    
        //释放锁
        public synchronized  void unLock(){
            //将当前锁资源释放
            isLocked=false;
            //唤起正在等待使用锁的线程
            notify();
        }
    
    }
    public class MyLockTest {
    MyLock myLock=new MyLock();
        //A业务
        public void print() throws InterruptedException {
            //获取一把锁
            myLock.lock();
            System.out.println("print业务方法");
            doAdd();
            //释放锁
            myLock.unLock();
        }
        //B业务方法
        public void  doAdd() throws InterruptedException {
            //获取一把锁
            myLock.lock();
            System.out.println("aoAdd方法");
            //释放锁
            myLock.unLock();
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyLockTest myLockTest=new MyLockTest();
            myLockTest.print();
        }
    }

      控制台结果:

    当前效果就造成了死锁

      synchronized可重入性:如果当前A持有一把锁,在A业务内部调用B,那么B也同样拥有这把锁的使用权限

      编写测试代码:

    MyLock myLock=new MyLock();
        //A业务
        public synchronized void print() throws InterruptedException {
    
            System.out.println("print业务方法");
            doAdd();
    
        }
        //B业务方法
        public synchronized void  doAdd() throws InterruptedException {
    
            System.out.println("aoAdd方法");
    
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyLockTest myLockTest=new MyLockTest();
            myLockTest.print();
        }

      控制台结果:

     

      ReentrantLock同样具有可重入性

      编写测试代码:

    public class MyLockTest {
        //创建锁对象
        Lock lock=new ReentrantLock();
        //A业务
        public void print() throws InterruptedException {
            //获取了一把锁
            lock.lock();
            System.out.println("print业务方法");
            doAdd();
            //释放锁
            lock.unlock();
    
        }
        //B业务方法
        public void  doAdd() throws InterruptedException {
            //获取一把锁
            lock.lock();
            System.out.println("aoAdd方法");
            //释放锁
            lock.unlock();
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyLockTest myLockTest=new MyLockTest();
            myLockTest.print();
        }
    }

      控制台结果:

    3. 读写锁

      并发线程下,所有线程都执行读的操作,会不会有问题
      并发线程下,部分读部分写会不会有问题 会发生写冲突
      并发线程下,所有线程都执行写会不会有问题 会发生写冲突

      编写测试代码:

     //创建一个集合
        static Map<String,String> map=new HashMap<String,String>();
        //创建一个读写锁
        static ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
        //获取读锁
        static Lock readLock=lock.readLock();
        //获取写锁
        static Lock writeLock=lock.writeLock();
        //写操作
        public Object put(String key,String value){
            writeLock.lock();
            try {
                System.out.println("Write正在执行写操作~");
                Thread.sleep(100);
                String put = map.put(key, value);
                System.out.println("Write写操作执行完毕~");
                return put;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                writeLock.unlock();
            }
            return null;
    
        }
    
        //写操作
        public Object get(String key){
            readLock.lock();
            try {
                System.out.println("Read正在执行读操作~");
                Thread.sleep(100);
                String value = map.get(key);
                System.out.println("Read读操作执行完毕~");
                return value;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                readLock.unlock();
            }
            return null;
    
        }
    
        public static void main(String[] args) {
            ReadWriteLock lock=new ReadWriteLock();
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                new Thread(()->{
                    try {
                        //写操作
                        lock.put(finalI +"","value"+finalI);
                        //读操作
                        System.out.println(lock.get(finalI+""));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
    
        }

      控制台 结果:

    我们可以看出是当所有写操作都执行完毕后才开始执行读操作

    4. 乐观锁

      总认为不会发生并发问题,每一次取数据时总认为其他线程不会对该数据先进性更改,但是在更新时会判断其他线程在这之前有

      没有对该数据进行修改,
      数据库当中常用方案:版本号控制

    5.悲观锁
      总是假设最坏的情况,每次取数据时,都会认为其他线程会对该数据进行修改,所以会进行加锁
      其他线程访问的时候会阻塞等待,例如在数据库当中可以使用行锁,表锁以及读写锁等方式实现

    6. CAS无锁模式

      6.1  什么 是CAS

      CAS:Compare and Swap,即比较再交换。

      jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。

      JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

      6.2  Java内存模型:JMM(Java Memory Model)

        在内存模型当中定义了一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一块工作内存,工作内存当中主内存数据的副本
        当更新数据时,会将工作内存中的数据同步到主内存当中

      6.2 CAS无锁机制

        本身无锁,采用乐观锁的思想,在数据操作时对比数据是否一致,如果一致代表之前没有线程操作该数据,那么就会更新数据,如果不一致代表有县城更新则重试
      CAS当中包含三个参数CAS(V,E,N),V标识要更新的变量,E标识预期值,N标识新值

      

      运行过程:
      1.线程访问时,先会将主内存中的数据同步到线程的工作内存当中
      2.假设线程A和线程B都有对数据进行更改,那么假如线程A先获取到执行权限
      3.线程A先会对比工作内存当中的数据和主内存当中的数据是否一致,如果一致(V==E)则进行更新,不一致则刷新数据,重新循环判断
      4.这时更新完毕后,线程B也要进行数据更新,主内存数据和工作内存数据做对比,如果一致则进行更新,不一致则将主内存数据重新更新到工作内存,然后循环再次对比两个内存中的数据
      直到一致为止


      CAS无锁机制存在一个问题
      ABA问题,如果将原来A的值改为了B,然后又改回了A,虽然最终结果没有发生改变,但是在过程中是对该数据进行了修改操作
      解决该问题:在Java中并发包下有一个原子类:AtomicStampedReference,在该类当中通过版本控制判断值到底是否被修改
      解释:如果对值进行了更改则版本号+1,那么在CAS当中不仅仅对比变量的值,还要对比版本号,如果值和版本号都相等则代表没有被修改,如果有一方不相等代表进行过更改
      那么就从主内存中重新刷新数据到工作内存然后循环对比,直到成功为止~

      7.保证线程安全的三个方面:
      1.原子性:保证同一时刻该资源只能有一个线程访问修改,其他线程阻塞等待,例如Atomic包,锁
      2.可见性:一个线程对于主内存的数据操作对于其他线程是可见的
      3.有序性:一个线程观察其他线程中指令执行顺序,由于指令重排序存在,观察结果一般杂乱无序
      原子性: 互斥访问,Atomic包,CAS算法,Synchronized,Lock

      可见性:synchronized,volatile

      顺序性:happends-before

      8.  原子类

       编写测试代码:  

     //定义一个原子类对象
        private AtomicInteger  atomicInteger=new AtomicInteger();
        public  void getCount(){
            //+1再返回
            System.out.println(atomicInteger.incrementAndGet());
        }
    
        public static void main(String[] args) {
            AtomicTest atomicTest=new AtomicTest();
            for (int i=1;i<=2;i++){
                new Thread(()->{
                    for (int j=1;j<=100;j++){
                        atomicTest.getCount();
                    }
                }).start();
            }
        }

      控制台效果:

     

      8.AQS:全成AbstractQueueSynchronizer,抽象队列同步器,这个类在java.util.concurrent.locks包下
        它是一个底层同步工具类,比如CountDownLatch,Sammphore,ReentrantLock,ReentrantReadWriteLock等等都是基于AQS
        底层三个内容:
        1.state(用于计数器)
        2.线程标记(哪一个线程加的锁)
        3.阻塞队列(用于存放阻塞线程)

  • 相关阅读:
    重拾Ajax
    和transformjs一起摇摆
    CSS/JS图片鼠标悬浮一道光闪过
    深究JS异步编程模型
    Vue.js组件
    并行计算基础&amp;编程模型与工具
    Oracle442个应用场景------------基础应用场景
    消息摘要算法-HMAC算法
    linux上网络配置不生效的怪异现象处理
    Eclipse 将projectBuild Path中引用的jar包自己主动复制到WEB-INF下的lib目录下
  • 原文地址:https://www.cnblogs.com/szhhhh/p/12566905.html
Copyright © 2011-2022 走看看