锁机制

Posted qsc-acstu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了锁机制相关的知识,希望对你有一定的参考价值。

悲观锁:

      悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁

       每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

     Select * from xxx for update;

     缺点:因为只能保证一个连接进行操作,所以效率低

乐观锁:

        乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制

重入锁:

       重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但外层函数不受内层函数影响,例如当内部释放锁(unlock)后,外部不会释放。在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。

synchronized :

public class Test 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) 
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    

ReentrantLock :

public class Test02 extends Thread 
    ReentrantLock lock = new ReentrantLock();
     //外层
    public void get() 
        lock.lock();
        System.out.println(Thread.currentThread().getId());
        set();
        lock.unlock();
    
      //内层
    public void set() 
        lock.lock();
        System.out.println(Thread.currentThread().getId());
        lock.unlock();
    
    @Override
    public void run() 
        get();
    
    public static void main(String[] args) 
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    

读写锁:

        两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。

CAS无锁机制:

原子类底层实现保证线程安全就是通过CAS实现。

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

对应java内存模型,V相当于是主内存,E相当于本地内存,如果(V=E),本地内存与主内存一致,说明变量没有被修改过那么就将V要更新的变量的值设置成N新值,如果不相等,说明本地内存被修改,需要将主内存的值刷新到本地内存中去,再进行V和E进行比较,然后再设置成新值N。

一个来自码农翻身的例子:

(1)从内存中读取value值,假设为10,称之为A

(2)B=A+1,得到B=11

(3)用A的值和内存的值相比,如果相等(过去的一段时间内,没人修改过A),就把B写入内存,如果不相等的,说明A在这段时间内被修改了,就放弃这次修改,返回第一步

     CAS存在一个很明显的问题,即ABA问题。

  问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?         

          如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

自旋锁:

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

private AtomicReference<Thread> sign =new AtomicReference<>();
    public void lock() 
        Thread current = Thread.currentThread();
        while (!sign.compareAndSet(null, current)) 
          
    
    public void unlock() 
        Thread current = Thread.currentThread();
        sign.compareAndSet(current, null);
    

public class Test implements Runnable 
    static int sum;
    private SpinLock lock;

    public Test(SpinLock lock) 
        this.lock = lock;
    

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException 
        SpinLock lock = new SpinLock();
        for (int i = 0; i < 100; i++) 
            Test test = new Test(lock);
            Thread t = new Thread(test);
            t.start();
        

        Thread.currentThread().sleep(1000);
        System.out.println(sum);
    

    @Override
    public void run() 
        this.lock.lock();
            this.lock.lock();
        sum++;
        this.lock.unlock();
        this.lock.unlock();
    

技术图片

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

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

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

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

分布式锁:

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

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

以上是关于锁机制的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发编程线程锁机制 ( 线程安全 | 锁机制 | 类锁 | 对象锁 | 轻量级锁 | 重量级锁 )

锁机制,信号机制及事件机制

锁机制(自旋锁-乐观锁-悲观锁)

锁机制,信号机制,事件机制

JAVA中的内锁机制是啥

MySQL InnoDB锁机制