码迷,mamicode.com
首页 > 其他好文 > 详细

锁机制

时间:2019-08-10 10:13:21      阅读:99      评论:0      收藏:0      [点我收藏+]

标签:查询   res   sys   不可   子类   image   乐观锁   函数   可重入锁   

悲观锁:

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

       每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会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分布式锁

锁机制

标签:查询   res   sys   不可   子类   image   乐观锁   函数   可重入锁   

原文地址:https://www.cnblogs.com/QSC-AcStu/p/11330446.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!