线程基础(乐观锁和悲观锁)

线程基础(乐观锁和悲观锁),第1张

线程基础(乐观锁和悲观锁) 悲观锁(Pessimistic Lock)

在对一条数据进行修改时,为了避免其他人同时对这一条数据进行更改,我们就可以通过锁机制,对数据进行上锁防止以并发问题; 

线程认为线程的安全问题是很容易发生的,会对代码上锁。

因为悲观锁的上锁和释放锁的过程是需要消耗很多资源的,因此会降低程序的性能。

(锁机制看上篇)

乐观锁(Optimistic Locking)

相对于悲观锁来说,乐观锁认为线程问题是不容易发生的,因此不会对代码上锁。

乐观锁本身是不会对 *** 作的数据进行加锁,而是通过一些业务的 *** 作实现的,如果线程过多,也不建议使用;

实现的方式: 

  • 版本号机制

    利用版本号记录数据更新的次数,一旦更新版本号加1,线程修改数据后会判断版本号是否是自己更新的次数,如果不是就不更新数据。

  • CAS (Compare And Swap)比较和交换算法

    • 通过内存的偏移量获得数据的值

    • 计算出一个预计的值

    • 将提交的实际值和预计值进行比较,如果相同执行修改,如果不同就不修改

悲观锁和乐观锁的对比 

  • 悲观锁更加重量级,占用资源更多,应用线程竞争比较频繁的情况,多写少读的场景

  • 乐观锁更加轻量级,性能更高,应用于线程竞争比较少的情况,多读少写的场景

public class AtomicDemo {

    static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            new Thread(() ->{
                count++;
            }).start();
        }
        System.out.println(count);
    }
}

问题:多线程同时执行++ *** 作,最后结果少了

分析:

count++ 分解为三个指令:

  1. 从内存中读取count的值

  2. 计算count+1的值

  3. 将计算结果赋值给count

这三个指令不是原子性的,A线程读取count值10,加1后得到11,准备赋值给count;B线程进入读取count也是10,加1得到11,赋值给count为11;切换会A线程,赋值count为11。

解决方案:

  1. 悲观锁,使用同步方法、同步块、同步锁

  2. 乐观锁

    使用原子整数

 悲观锁解决方案:

public class Demo {
    static Integer count=0;
    static Object object=new Object();
    Lock lock=new ReentrantLock();
    public void aa(){
        //同步锁
        lock.lock();
        try{
            count++;
        }finally {
            lock.unlock();
        }
    }
//    同步方法
    public synchronized void cc(){
        count++;
    }
//    同步代码块
    public void dd(){
        synchronized (object){
            count++;
        }
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        for (int i = 0; i < 100000; i++) {
            new Thread(()->{
                demo.dd();
            }).start();
        }
        System.out.println("count:"+count);
        
    }
}

乐观锁的解决方案:

原子类 

java.util.concurrent.atomic包下的类大多是使用 CAS *** 作来实现的;

如AtomicInteger类:

常用方法:

  • incrementAndGet 原子递增

  • decrementAndGet 原子递减

public class AtomicDemo {

    static AtomicInteger atomicInteger=new AtomicInteger(0);

    //乐观锁
    public void bb(){
        count++;
        atomicInteger.incrementAndGet();
        }
    public static void main(String[] args) {
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int i = 0; i < 100000; i++) {
            new Thread(()->{
                atomicDemo.bb();
            }).start();
        }
        System.out.println("count:"+count);
        System.out.println("atomic:"+atomicInteger.get());
    }
}

CAS算法存在的问题 

1.ABA问题 :

count=3;Thread1获取count=3 

                Thread2获取count=3 再修改成 count=4;再将count=4 =>count=3;

                Thread1再进行 *** 作,然后执行成功了,但这并不代表这个过程没有问题;

             

2.如果预期值和实际值不一致处于循环等待状态,对CPU的消耗比较大

 编写懒汉式的单例模式,创建100个线程,每个线程获得一个单例对象,看是否存在问题(打印对象的hashCode,看是否相同)
public class SingletonDemo {
    private static SingletonDemo singleton = null;

    private SingletonDemo() {
        System.out.println("创建对象");
    }

    public static SingletonDemo getInstance(){
        if(singleton==null){
            singleton = new SingletonDemo();
        }
        return singleton;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"-----"+SingletonDemo.getInstance().hashCode());
            }).start();
        }
    }
}

我们会发现:

多线程的情况下,不止一次调用了构造方法,产生了多个对象,就不是单例了(如果没有发生这种情况,是因为第一次创建的过程太快了,让这个线程sleep,就会发现了)

这是因为第一次在创建对象过程中时 又执行了别的线程别的线程也进行了判断,也创建了对象

if(singleton==null){
    singleton = new SingletonDemo();
}

解决问题:

可以通过同步方法、同步代码块、同步锁的方式的方式都可已解决

public synchronized static SingletonDemo getInstance(){
        if(singleton==null){
            singleton = new SingletonDemo();
        }
        return singleton;
    }

但是这种方法就会产生一个问题:

每次执行的时候都需要大量上锁、释放锁;我们知道大量的上锁、释放锁会消耗资源,而单例模式用的是一个对象,因此这样的方式显然也不是最佳解。

那么我们的单例类的方法这样写:

public synchronized static SingletonDemo getInstance(){
        //如果为空,再执行代码,提升性能
        if(singleton==null){
            //同步代码块 保证原子性
            synchronized (SingletonDemo.class){
                //创建对象
                if(singleton==null){
                    singleton = new SingletonDemo();
                }
            }
        }

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5659468.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存