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.阻塞队列(用于存放阻塞线程)

  • 相关阅读:
    centos7.6 安装与配置 MongoDB yum方式
    MongoDB 介绍
    centos 关闭selinux
    前端 HTML标签属性
    前端 HTML 标签嵌套规则
    前端 HTML 标签分类
    前端 HTML body标签相关内容 常用标签 表单标签 form里面的 input标签介绍
    前端 HTML body标签相关内容 常用标签 表单标签 form 表单控件分类
    前端 HTML form表单标签 select标签 option 下拉框
    POJ 1426
  • 原文地址:https://www.cnblogs.com/szhhhh/p/12566905.html
Copyright © 2011-2022 走看看