java各种锁

共 18177字,需浏览 37分钟

 ·

2021-04-29 17:31

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

76套java从入门到精通实战课程分享

java有哪些锁的分类:

  1. 悲观与乐观锁

  2. 公平锁与非公平锁

  3. 自旋锁/重入锁

  4. 重量级锁与轻量级锁

  5. 独占锁与共享锁

乐观锁悲观锁:

什么是悲观锁,什么是乐观锁

悲观锁:

  • mysql的角度分析: 悲观锁就是比较悲观,当多个线程同一个数据实现修改的时候,最后只有一个线程才能修改成功,只要谁能够获取到行锁 则其他线程时不能够对数据做任何修改操作,且是阻塞状态

  • java锁层面:如果没有获取到锁,则会阻塞等待,后期唤醒的锁成本就会非常高,从新被我们CPU从就绪调度为运行状态

Lock synchronized 锁 悲观锁 没有获取到锁的线程会阻塞等待;

乐观锁:

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



乐观锁:获取锁–如果没有获取到锁,当前线程是不会阻塞等待,通过死循环控制

乐观锁属于无锁机制,没有竞争锁的流程

注意:mysql 的 innodb 引擎中存在行锁的概念/

Mysql层面如何实现乐观锁了?

在我们表结构中,会新增一个字段就是版字段

``version varchar(255) DEFAULT NULL,`

多线程对同一行数据实现修改操作,提前查询当前最新的version版本号码

作为update条件查询,如果当前version版本号码发生了变化,则查询不到该数据,

表示如果修改数据失败,则不断重试,有从新查询最新的版本实现update

需要注意的是 控制乐观锁的循环的次数,避免cpu飙高的问题

mysql的innodb引擎中存在行锁的概念


乐观锁的实现方式:

乐观锁,使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。
记录1,id,status1,status2,stauts3,version,表示有三个不同的状态,以及数据当前的版本
操作1:update table set status1=1,status2=0,status3=0 where id=111;
操作2:update table set status1=0,status2=1,status3=0 where id=111;
操作3:update table set status1=0,status2=0,status3=1 where id=111;
没有任何控制的情况下,顺序执行3个操作,最后前两个操作会被直接覆盖。
加上version字段,每一次的操作都会更新version,提交时如果version不匹配,停止本次提交,可以尝试下一次的提交,以保证拿到的是操作1提交后的结果。
这是一种经典的乐观锁实现。


另外,java中的compareandswap即cas,解决多线程并行情况下使用锁造成性能损耗的一种机制。
CAS操作包含三个操作数,内存位置(V),预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会西东将该位置值更新为新值。否则,处理器不做任何操作。
记录2: id,stauts,status 包含3种状态值 1,2,3
操作,update status=3 where id=111 and status=1;
即 如果内存值为1,预期值为1,则修改新值。对于没有执行的操作则丢弃。

公平锁与非公平锁

公平锁与非公平锁的直接的区别

公平锁: 就是比较公平,根据请求锁的顺序排列,先来请求先来请求的就先获取锁,后来获取锁就最 后获取到, 采取队列存放…类似类似于吃饭排队。


队列---底层实现方式---数组或者链表实现


非公平锁:不是据请求的顺序排列,通过争抢的方式获取锁
非公平锁效率是公平锁效率要高,Synchronized 是非公平锁
New ReentramtLock()(true)—公平锁
New ReentramtLock()(false)—非公平锁
底层基于 aqs 实现
独占锁与共享锁
独占锁与共享锁之间的区别
独占锁: 在多线程中,只允许有一个线程获取到锁,其他线程都会等待。
共享锁: 多个线程可以同时持有锁,
例如 ReentrantLock 读写锁。读读可以共享、写写互斥、读写互斥
/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读  可以共存!
 * 读-写  不能共存!
 * 写-写  不能共存!
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

// 加锁的
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();

    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 取,读,所有人都可以读!
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

/**
 * 自定义缓存
 */
class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();

    // 存,写
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入OK");
    }

    // 取,读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取OK");
    }
}


可重入性

在同一个线程中锁可以不断传递的,可以直接获取Syn/lock ``aqs


CAS(自旋锁)

CAS: 没有获得到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁


CAS:Compare and Swap,翻译成比较并交换,执行函数CAS(V,E,N)


三个操作数: 内存值V, 旧的预期值E,要修改的新值N当且仅当预期值E和内存值V相同时,将内存值V修改为N,负责什么都不做


(1)CAS是通过硬件指令,保证了原子性

(2)java是通过unsafe jni技术

原子类: AtomicBoolean,AtomicInteger,AtomicLong等使用 CAS 实现


unsafe类



优点:

没有获取到锁的资源,会一直子啊用户态,不会阻塞,没有锁的线程会一直通过循环控 制重试


缺点:

通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题;


Cas 本质的原理:

旧的预期值===>>>V(共享变量中值),才会修改我们的V


基于 cas 实现锁机制原理

Cas 无锁机制原理:

  • 定义一个锁的状态

  • 状态状态值=0,则表示没有线程获取到该锁

  • 状态状态值=1,则表示有线程已经持有该锁

实现细节:

CAS获取锁:

将该锁的状态从0到1----能够修改成功 cas 成功则表示获取锁成功

如果获取锁失败–修改失败,则不会阻塞而是通过循环==(自旋控制重试)==


CAS 释放锁:

将该锁的状态从 1 改为 0 如果能够改成功 cas 成功则表示释放锁成功。


CAS 如何解决 ABA 的问题

CAS主要检查内存值V 与旧的预值值=E是否一致,如果一致的情况下,则修改


这时会存在ABA的问题:

如果将原来的值A,改为B,B有改为了A发现没有发生变化,实际上已经发生了变化,所以存在ABA问题


解决方法,通过版本号,对没个变量更新的版本号+1


引用原子引用,对应的思想:乐观锁


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

package com.nie.juc.cas;/*
 *
 *@auth  wenzhao
 *@date 2021/4/25  17:19
 */

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示 aba 的问题
 * (1)第一个参数 expectedReference:表示预期值。
 * (2)第二个参数 newReference:表示要更新的值。
 * (3)第三个参数 expectedStamp:表示预期的时间戳。
 * (4)第四个参数 newStamp:表示要更新的时间戳。
 */
public class CASDemo {

    //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
    // 注意:如果引用类型是 Long、Integer、Short、Byte、Character 一定一定要注意值的缓存区间!
    // 比如 Long、Integer、Short、Byte 缓存区间是在-128~127,
    // 会直接存在常量池中,而不在这个区间内对象的值 则会每次都 new 一个对象,
    // 那么即使两个对象的值相同,CAS 方法都会返回 false
    // 先声明初始值,修改后的值和临时的值是为了保证使用 CAS 方法不会因为对象不一样而返回 false


    // 正常在业务操作,这里面比较的都是一个个对象
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
        ;
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Lock lock = new ReentrantLock(true);

            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println("a2=>----" + atomicStampedReference.getStamp());


            System.out.println("更改" + atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3=>" + atomicStampedReference.getStamp());

        }, "a").start();


        // 乐观锁的原理相同!
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("+++++++++++++++++++++++++++++++++++++++++++++");
            System.out.println(atomicStampedReference.compareAndSet(1, 6,
                   stamp, stamp + 1));

            System.out.println("b2=>" + atomicStampedReference.getStamp());

        }, "b").start();

    }
}



输出:

a1=>1
b1=>1
a2=>----2
更改true
a3=>3
+++++++++++++++++++++++++++++++++++++++++++++
false
b2=>3


利用原子类手写 CAS 无锁

package com.nie.juc.cas;/*
 *
 *@auth  wenzhao
 *@date 2021/4/25  20:36
 */

import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;

public class AtomicTryLock {
    private AtomicLong cas = new AtomicLong(0);
    private Thread lockCurrentThread;

    /**
     * 1 表示锁已经被获取
     * 0 表示锁没有获取
     * 利用 cas 将 0 改为 1 成功则表示获取锁
     *
     * @return
     */

    //加锁
    private boolean tryLock() {
        boolean result = cas.compareAndSet(0, 1);
        if (result) {
            lockCurrentThread = Thread.currentThread();
        }
        return result;
    }

    //释放锁
    private boolean unLock() {
        if (lockCurrentThread != Thread.currentThread()) {
            return false;
        }
        return cas.compareAndSet(0, 1);
    }

    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) {
                    atomicTryLock.lockCurrentThread = Thread.currentThread();
                    System.out.println(Thread.currentThread().getName() + ",获取锁成功~");
                } else {
                    System.out.println(Thread.currentThread().getName() + ",获取锁失败~");
                }
            } catch (Exception e) {
            } finally {
                //释放锁
                if (atomicTryLock != null) {
                    atomicTryLock.unLock();
                }
            }
        }).start());
    }

}



输出

Thread-0,获取锁成功~
Thread-3,获取锁失败~
Thread-2,获取锁失败~
Thread-1,获取锁失败~
Thread-4,获取锁失败~
Thread-7,获取锁失败~
Thread-8,获取锁失败~
Thread-6,获取锁失败~
Thread-5,获取锁失败~


————————————————

版权声明:本文为CSDN博主「面相薪水编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

https://blog.csdn.net/qq_44236958/article/details/116138494





粉丝福利:Java从入门到入土学习路线图

👇👇👇

👆长按上方微信二维码 2 秒


感谢点赞支持下哈 

浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报