zoukankan      html  css  js  c++  java
  • 常见的几种锁

    1.悲观锁  for update

        悲观锁认为每次查询数据数据都会造成数据的更新或者丢失问题,所以每次查询都会加上排它锁。

        

        如图所示,当两条线程同时访问该sql语句时,可能会造成脏读数据user_money为原来的两倍(假设线程一执行完第一句等待,线程二将两句全部执行完,这时线程一如果继续执行则会脏读数据)

       使用悲观锁则通过在其后加for update后,仅允许一个连接查询数据也就是只要一个连接获得锁后,其他连接则只能等待该锁的释放。

       缺点:每次都只有一个连接进行操作,效率非常低,适合查询量低的情况。

    2.乐观锁  version

       乐观锁认为每次查询都不会造成数据更新丢失,使用版本字段控制。(version机制)

       

        如图所示,每一次执行更新操作时,都要将version字段加一,这样其他连接就不能通过之前的version查找到该条数据,从而保证不重复读写。

       优点:可并发运行,效率高          缺点:需要增加一个维护字段version,而且可能会出现查不到数据的情况。

    3.重入锁 ReentrantLock

      

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

    非重入锁进行以上操作的话就会产生死锁。

    简单的说就是当外层获取到某一把锁,若该锁是可重入锁,则默认内层所有代码都获取了该锁。

    可重入锁有一个状态计数器,每当获取一次该锁,计数器的数值就会加一。每次释放锁,计数器的数值就会减一,只有当计数器的值减到0的时候,才会真正释放该锁,其他线程才可重新获取该锁。

    如果一把锁是不可重入锁时,当外层获取该锁后,内层的代码再次获取该锁时,由于外层没有释放,内层就获取不到而阻塞,导致程序等待,而外层也因此无法释放锁,就产生了死锁。

     1 public class Test02 extends Thread {
     2     ReentrantLock lock = new ReentrantLock();
     3     public void get() {
     4         lock.lock();
     5         System.out.println(Thread.currentThread().getId());
     6         set();
     7         lock.unlock();
     8     }
     9     public void set() {
    10         lock.lock();
    11         System.out.println(Thread.currentThread().getId());
    12         lock.unlock();
    13     }
    14     @Override
    15     public void run() {
    16         get();
    17     }
    18     public static void main(String[] args) {
    19         Test ss = new Test();
    20         new Thread(ss).start();
    21         new Thread(ss).start();
    22         new Thread(ss).start();
    23     }
    24  
    25 }

    4.读写锁 ReentrantReadWriteLock

        假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。

        但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。

        常用于缓存设计。

      1 public class Cache {
      2     private static volatile Map<String,Object> map=new HashMap<>();
      3  
      4     private static ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
      5  
      6     private static Lock r=reentrantReadWriteLock.readLock();
      7     private static Lock w=reentrantReadWriteLock.writeLock();
      8     /**
      9      * 写
     10      * @param key
     11      * @param object
     12      */
     13     public static void put(String key,Object object){
     14         try{
     15 //            w.lock();
     16             System.out.println("正在写入key:"+key+",value:"+object+"开始。。");
     17             Thread.sleep(100);
     18             Object obj=map.put(key,object);
     19             System.out.println("写入key:"+key+",value:"+object+"结束。。");
     20         } catch (Exception e) {
     21             e.printStackTrace();
     22         } finally {
     23 //            w.unlock();
     24         }
     25     }
     26  
     27     /**
     28      * 读
     29      * @param key
     30      * @return
     31      */
     32     public static Object get(String key){
     33         try{
     34 //            r.lock();
     35             System.out.println("正在读取key:"+key+"开始。。");
     36             Thread.sleep(100);
     37             Object obj=map.get(key);
     38             System.out.println("读取key:"+key+",value:"+obj+"结束。。");
     39             return obj;
     40         } catch (Exception e) {
     41             e.printStackTrace();
     42         } finally {
     43 //            r.unlock();
     44         }
     45         return null;
     46     }
     47  
     48     public static void main(String[] args) {
     49         new Thread(new Runnable() {
     50             @Override
     51             public void run() {
     52                 for (int i = 0; i < 10; i++) {
     53                     Cache.put(i+"",i+"");
     54                 }
     55             }
     56         }).start();
     57         new Thread(new Runnable() {
     58             @Override
     59             public void run() {
     60                 for (int i = 0; i < 10; i++) {
     61                     System.out.println(Cache.get(i+""));
     62                 }
     63             }
     64         }).start();
     65     }
     66 }
     67  
     68 输出如下:
     69 正在写入key:0,value:0开始。。
     70 正在读取key:0开始。。
     71 读取key:0,value:null结束。。
     72 写入key:0,value:0结束。。
     73 正在写入key:1,value:1开始。。
     74 null
     75 正在读取key:1开始。。
     76 读取key:1,value:null结束。。
     77 null
     78 写入key:1,value:1结束。。
     79 正在写入key:2,value:2开始。。
     80 正在读取key:2开始。。
     81 写入key:2,value:2结束。。
     82 读取key:2,value:null结束。。
     83 null
     84 正在写入key:3,value:3开始。。
     85 正在读取key:3开始。。
     86 写入key:3,value:3结束。。
     87 读取key:3,value:null结束。。
     88 null
     89 正在读取key:4开始。。
     90 正在写入key:4,value:4开始。。
     91 写入key:4,value:4结束。。
     92 读取key:4,value:4结束。。
     93 4
     94 正在写入key:5,value:5开始。。
     95 正在读取key:5开始。。
     96 读取key:5,value:null结束。。
     97 null
     98 正在读取key:6开始。。
     99 写入key:5,value:5结束。。
    100 正在写入key:6,value:6开始。。
    101 写入key:6,value:6结束。。
    102 正在写入key:7,value:7开始。。
    103 读取key:6,value:6结束。。
    104 6
    105 正在读取key:7开始。。
    106 读取key:7,value:null结束。。
    107 null
    108 正在读取key:8开始。。
    109 写入key:7,value:7结束。。
    110 正在写入key:8,value:8开始。。
    111 写入key:8,value:8结束。。
    112 正在写入key:9,value:9开始。。
    113 读取key:8,value:8结束。。
    114 8
    115 正在读取key:9开始。。
    116 写入key:9,value:9结束。。
    117 读取key:9,value:9结束。。
    118 9

        可以看到,在进行写操作的时候进行了读取操作,这样就造成数据不安全,将注释掉的锁代码打开后就可以实现读写分离保证数据安全。

    5. CAS 无锁机制

        CAS :  Compare And Swap  原子类底层使用CAS无锁机制实现保证线程安全,CAS无锁机制效率比有锁机制高。

    (1)与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。

        更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。

    (2)无锁的好处:

    第一,在高并发的情况下,它比有锁的程序拥有更好的性能;

    第二,它天生就是死锁免疫的。

    就凭借这两个优势,就值得我们冒险尝试使用无锁的并发。

    (3)CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。

    (4)CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

    (5)简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了。

    (6)在硬件层面,大部分的现代处理器都已经支持原子化的CAS指令。在JDK 5.0以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在。

    6.自旋锁 AtomicReference

        

     1 class SpinLock{
     2     private AtomicReference<Thread> sign=new AtomicReference<>();
     3     public void lock(){
     4         Thread thread=Thread.currentThread();
     5         while(!sign.compareAndSet(null,thread)){
     6  
     7         }
     8     }
     9  
    10     public void unlock(){
    11         Thread thread=Thread.currentThread();
    12         sign.compareAndSet(thread,null);
    13     }
    14 }
    15 public class Test implements Runnable{
    16     static int sum;
    17     private SpinLock lock;
    18     public Test(SpinLock lock){
    19         this.lock=lock;
    20     }
    21     public static void main(String[] args) throws InterruptedException {
    22         SpinLock spinLock=new SpinLock();
    23         for (int i = 0; i < 100; i++) {
    24             Test test=new Test(spinLock);
    25             Thread thread=new Thread(test);
    26             thread.start();
    27         }
    28         Thread.sleep(1000);
    29         System.out.println(sum);
    30     }
    31  
    32     @Override
    33     public void run() {
    34         this.lock.lock();
    35         this.lock.lock();
    36         sum++;
    37         this.lock.unlock();
    38         this.lock.unlock();
    39     }
    40 }

        

    当一个线程调用这个不可重入的自旋锁去加锁的时候没问题,当再次调用lock()的时候,因为自旋锁的持有引用已经不为空了,该线程对象会误认为是别人的线程持有了自旋锁

    使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。

    当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。

    由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

     

    7.分布式锁

    如果想在不同的jvm中保证数据同步,使用分布式锁技术。

    有数据库实现、缓存实现、Zookeeper分布式锁

    具体各种实现方式请自行百度 

  • 相关阅读:
    004 连接查询
    003 常用函数说明
    003 限定查询
    002 基础查询
    001 基础数据表脚本
    001 redis的简介和重点
    006 表单组件
    005 基本表单
    004 表格元素
    谚语,
  • 原文地址:https://www.cnblogs.com/xiaobai1202/p/10803597.html
Copyright © 2011-2022 走看看