• java锁的深度化-重入锁,读写锁,乐观锁,悲观锁


    1.重入锁

      目的:避免死锁的现象

      锁作为并发共享数据,保证一致性的工具,在java平台有多种实现synchronized(重量级)和ReentrantLock(轻量级)等等,这些已经写好提供的锁为我们开发提供了便利;

      重入锁:也叫作递归所,指的是同一线程外层函数获得锁之后,内层递归函数仍然有取该锁的代码,但不受影响;;

      在java环境下,ReentrantLock和synchronized都是可重入锁;

      1.1 不可重入锁

    package com.wn.lock;
    
    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();
        }
    }
    package com.wn.lock;
    
    public class MyLockTest {
        MyLock myLock=new MyLock();
    
        //业务一
        public void print() throws InterruptedException {
            //获取一把锁
            myLock.lock();
            System.out.println("print业务方法~~");
            doAdd();
            //释放锁
            myLock.unlock();
        }
    
        //业务二
        public void doAdd() throws InterruptedException {
            myLock.lock();
            System.out.println("doAdd业务方法~~");
            myLock.unlock();
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyLockTest test = new MyLockTest();
            test.print();
        }
    }

        

      1.2 synchronized实例

    package com.wn.lock;
    
    /*
    * 锁作为并发共享数据,保证一致性工具,在java平台有多种实现和ReentrantLock轻量级等等。这些已经写好提供的锁为我们开发提供了便利
    * 重入锁:也叫递归锁,指的是同一线程,外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响
    * 在java环境下ReentrantLock和synchronized都是可重入锁的
    * */
    public class ReentrantLockTest implements Runnable {
    
        //业务一
        public synchronized void get(){
            System.out.println("name:"+Thread.currentThread().getName()+"get();");
            set();
        }
    
        //业务二
        public synchronized void set(){
            System.out.println("name:"+Thread.currentThread().getName()+"set()");
        }
    
        @Override
        public void run() {
            get();
        }
    
        public static void main(String[] args){
            ReentrantLockTest test = new ReentrantLockTest();
            new Thread(test).start();
            new Thread(test).start();
            new Thread(test).start();
            new Thread(test).start();
        }
    }

        

      1.3 ReentrantLock实例

    package com.wn.lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockTest02 extends ReentrantLockTest {
        //创建锁对象
        ReentrantLock lock=new ReentrantLock();
    
        //业务一
        public void get(){
            //获取一把锁
            lock.lock();
            System.out.println(Thread.currentThread().getName());
            set();
            //释放一把锁
            lock.unlock();
        }
    
        //业务二
        public void set(){
            //获取一把锁
            lock.lock();
            System.out.println(Thread.currentThread().getName());
            //释放一把锁
            lock.unlock();
        }
    
        public void run(){
            get();
        }
    
        public static void main(String[] args){
            ReentrantLockTest test = new ReentrantLockTest();
            new Thread(test).start();
            new Thread(test).start();
            new Thread(test).start();
        }
    }

        

    2.读写锁

      相比java中的锁(Lock in java)里Lock实现,读写锁更复杂一些;

      假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时第一个资源没有任务问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其他线程对该资源进行读或写(也就是说:读和读能共享,读和写不能共享,写和写不能共享)。这就需要一个读写锁来解决这个问题。在java5中java.util.concurrent包中已经包含读写锁;

    package com.wn.lock;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /*
    * 相比java中的锁Locks in java 里lock实现,读写锁更负载一些;
    * 假设你的程序中涉及到一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该都允许多个线程能在同时读取共享数据,
    * 但是如果一个线程想去写这些共享资源,就不应该再有其他线程对该资源进行读或写;
    * */
    public class CacheTest {
        //创建map集合
        static Map<String,Object> map=new HashMap<String,Object>();
        //创建读写锁
        static ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
        //获取读操作
        static Lock r=rwl.readLock();
        //获取写操作
        static Lock w=rwl.writeLock();
    
        //获取一个key对应的value
        public static final Object get(String key){
            r.lock();
            try {
                System.out.println("正在做读的操作,key:"+key+"开始");
                Thread.sleep(100);
                Object object = map.get(key);
                System.out.println("正在做读的操作,key:"+key+"结束");
                System.out.println();
                return object;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                r.unlock();
            }
            return key;
        }
    
        //设置key对应的value,并返回旧的value
        public static final Object put(String key,Object value){
            w.lock();
            try {
                System.out.println("正在做写的操作,key:"+key+",value:"+value+"开始");
                Thread.sleep(100);
                Object o = map.put(key, value);
                System.out.println("正在做写的操作,key:"+key+",value:"+value+"结束");
                System.out.println();
                return o;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                w.unlock();
            }
            return value;
        }
    
        public static void main(String[] args){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<3;i++){
                        CacheTest.put(i+"",i+"");
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<3;i++){
                        CacheTest.get(i+"");
                    }
                }
            }).start();
        }
    }

        

    3.乐观锁

      乐观锁总是任务不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他新城在这之前有没有对数据进行修改,一般会使用版本号或CAS操作实现;

      乐观锁:本质没有锁,效率高,无阻塞,无等待,重试;

      version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时夜壶读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功;

    update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

      CAS操作方式:即compare and swap或者compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试;

    4.悲观锁

      总是假设最坏的情况,每次取数据时都任务其他线程会修改,所以都会加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁,读锁和写锁等,都是在操作之前加锁,在java中,synchronized的思想也是悲观锁;

      

  • 相关阅读:
    用C#实现在线升级
    wordwrap,wordbreak,whitespace,textoverflow的区别和用法[转]
    Sql Server 存储过程分页大全(2005,2000)
    C#中生成中文繁体web页面
    如何在c#里执行sql server DTS包
    asp.net采集函数(采集、分析、替换、入库)
    css定义一个导航栏
    mssql与access的sql语法差异
    [转]sql server数据库定时自动备份
    HttpHandler与图片盗链
  • 原文地址:https://www.cnblogs.com/wnwn/p/12565603.html
Copyright © 2020-2023  润新知