锁的分类&深入理解CAS无锁机制与ABA的问题

发布于:2023-01-09 ⋅ 阅读:(670) ⋅ 点赞:(0)

锁的分类

悲观锁

悲观锁比较悲观,当多个线程对同一行数据实现修改的时候,最后只有一个线程才能修改成功,只要谁能够对获取该到行锁则其他线程时不能够对该数据做任何修改操作,且是阻塞状态。 比如for update 或者事务开启了事务但是没有提交事务。

select * from information_schema.innodb_trx t
select t.trx_mysql_thread_id from information_schema.innodb_trx t
kill 768; --清理未提交的事务

乐观锁

乐观锁比较乐观,通过预值或者版本号比较,如果不一致性的情况则通过循环控制修改,当前线程不会被阻塞,是乐观,效率比较高。

UPDATE meite_user SET user_name=?, version=? WHERE user_id=? AND version=? AND deleted=0

SELECT user_id,user_name,user_age,user_addres,create_time,deleted,version FROM meite_user WHERE user_id=? AND deleted=0

@GetMapping("/optimisticLockUser")
public String optimisticLock(UserEntity userEntity) {
    Long userId = userEntity.getUserId();
    // 标记该线程是否修改成功
    Integer resultCount = 0;
    while (resultCount <= 0) {
        // 1.根据userid 查找到对应的VERION版本号码 获取当前数据的版本号码
        UserEntity dbUserEntity = userMapper.selectById(userId);
        if (dbUserEntity == null) {
            return "未查询到该用户";
        }
        // 2.做update操作的时候,放入该版本号码  乐观锁
        userEntity.setVersion(dbUserEntity.getVersion());
        resultCount = userMapper.updateById(userEntity);
    }
    return resultCount > 0 ? "success" : "fail";
}

公平与非公平锁

公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最后获取到, 采用队列存放 类似于吃饭排队。
非公平锁:不是根据根据请求的顺序排列, 通过争抢的方式获取锁。

New ReentramtLock()(true)—公平锁
New ReentramtLock()(false)—非公平锁
在这里插入图片描述

非公平锁效率是公平锁效率要高。Synchronized是非公平锁

public class MayiktLock implements Runnable {
    private static int count = 0;
    private static Lock lock = new ReentrantLock(true);

    @Override
    public void run() {

        while (count < 200) {
//            try {
//                Thread.sleep(100);
//            } catch (Exception e) {
//
//            }
//            lock.lock();
//            System.out.println(Thread.currentThread().getName() + ",count:" + count);
//            count++;
            createCount();
//            lock.unlock();
        }
    }

    public synchronized void createCount() {
        System.out.println(Thread.currentThread().getName() + ",count:" + count);
        count++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new MayiktLock()).start();
        }
    }
}



独占锁与共享锁

独占锁:在多线程中,只允许有一个线程获取到锁,其他线程都会等待。
共享锁:多个线程可以同时持有锁,例如ReentrantLock读写锁。读读可以共享、
写写互斥、读写互斥、写读互斥。

public class MyTask extends Thread {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        lock.readLock().lock();
        System.out.println(">>>" + Thread.currentThread().getName() + ",正在读取锁开始");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        System.out.println(">>>" + Thread.currentThread().getName() + ",正在读取锁结束");
        lock.readLock().unlock();
    }

    public void write() {
        lock.writeLock().lock();
        System.out.println(">>>" + Thread.currentThread().getName() + ",正在写入锁开始");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        System.out.println(">>>" + Thread.currentThread().getName() + ",正在写入锁结束");
        lock.writeLock().unlock();
    }

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> myTask.read()).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> myTask.write()).start();
        }
//        lock.writeLock()
    }

}

锁的可重入性

在同一个线程中锁可以不断传递的,可以直接获取。

自旋锁(Cas)

CAS: Compare and Swap,翻译成比较并交换。 执行函数CAS(V,E,N)
CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。
在这里插入图片描述
1.Cas 是通过硬件指令,保证原子性
2.Java是通过unsafe jni技术
3.原子类: AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 实现。

使用AtomicInteger实现自增

public class Test002 extends Thread {
    private static AtomicLong atomicLong = new AtomicLong();

    @Override
    public void run() {
        while (atomicLong.get() < 10000) {
            long l = atomicLong.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + "," + l);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Long startTime=System.currentTimeMillis();
        Test002 t1 = new Test002();
        Test002 t2 = new Test002();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        Long endTime=System.currentTimeMillis();
        System.out.println(endTime-startTime);
    }
}
public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

Var6 就是为旧的预期值:0 E
Var1,var2 就是我们共享变量值:V
Var6+Var4 就是我们的新值N

如果Var6(E)==Var1,var2 (V) 则修改V为N;

Cas Aba的问题

Cas主要检查 内存值V与旧的预值值=E是否一致,如果一致的情况下,则修改。
这时候会存在ABA的问题:
如果将原来的值A,改为了B,B有改为了A 发现没有发生变化,实际上已经发生了变化,所以存在Aba问题。
解决办法:通过版本号码,对每个变量更新的版本号码做+1

解决aba问题是否大:概念产生冲突,但是不影响结果,换一种方式 通过版本号码方式。

AtomicMarkableReference
(1)第一个参数expectedReference:表示预期值。
(2)第二个参数newReference:表示要更新的值。
(3)第三个参数expectedStamp:表示预期的时间戳。
(4)第四个参数newStamp:表示要更新的时间戳。

public class Test004 {
    // 注意:如果引用类型是Long、Integer、Short、Byte、Character一定一定要注意值的缓存区间!
    // 比如Long、Integer、Short、Byte缓存区间是在-128~127,会直接存在常量池中,而不在这个区间内对象的值则会每次都new一个对象,那么即使两个对象的值相同,CAS方法都会返回false
    // 先声明初始值,修改后的值和临时的值是为了保证使用CAS方法不会因为对象不一样而返回false
    private static final Integer INIT_NUM = 1000;
    private static final Integer UPDATE_NUM = 100;
    private static final Integer TEM_NUM = 200;
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(INIT_NUM, 1);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                Integer value = (Integer) atomicStampedReference.getReference();
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + " : 当前值为:" + value + " 版本号为:" + stamp);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // value 旧值 内存中的值   UPDATE_NUM 修改的值
                if (atomicStampedReference.compareAndSet(value, UPDATE_NUM, 1, stamp + 1)) {
                    System.out.println(Thread.currentThread().getName() + " : 当前值为:" + atomicStampedReference.getReference() + " 版本号为:" + atomicStampedReference.getStamp());
                } else {
                    System.out.println("版本号不同,更新失败!");
                }
            }
        }, "线程A").start();

    }
}

利用原子类手写CAS无锁

public class AtomicTryLock {

    /**
     * 定义AtomicInteger  修改为1表示该锁已经被使用该 修改为0表示为被使用
     */
    private volatile AtomicInteger atomicInteger = new AtomicInteger(0);
    private Thread lockCurrentThread;

    /**
     * 尝试获取锁
     *
     * @return
     */
    public boolean tryLock() {
        boolean result = atomicInteger.compareAndSet(0, 1);
        if (result) {
            lockCurrentThread = Thread.currentThread();
        }
        return result;
    }

    /**
     * 释放锁
     *
     * @return
     */
    public boolean unLock() {
        if (lockCurrentThread != null && lockCurrentThread != Thread.currentThread()) {
            return false;
        }
        return atomicInteger.compareAndSet(1, 0);
    }

    public static void main(String[] args) {
        AtomicTryLock atomicTryLock = new AtomicTryLock();
        IntStream.range(1, 10).forEach((i) -> new Thread(() -> {

            try {
                boolean result = atomicTryLock.tryLock();
                if (result) {
                    System.out.println(Thread.currentThread().getName() + ",获取锁成功~");
                } else {
                    System.out.println(Thread.currentThread().getName() + ",获取锁失败~");
                }
            } catch (Exception e) {
                e.printStackTrace();
                atomicTryLock.unLock();
            } finally {
                atomicTryLock.unLock();
            }

        }).start());
    }


}

利用CAS实现自旋锁


网站公告

今日签到

点亮在社区的每一天
去签到