多线程高阶——常见的锁 策略

本文探讨了多线程中的乐观锁和悲观锁策略。乐观锁主要通过CAS操作实现,例如Atomic*类,而悲观锁则以synchronized为代表。介绍了CAS的ABA问题及其解决方案,并对比了AtomicStampedReference与AtomicReference。还讨论了读写锁、公平锁与非公平锁、可重入锁的概念,以及JDK1.6后的锁升级过程,如偏向锁和轻量级锁。
摘要由CSDN通过智能技术生成
  • 乐观锁:乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则返回用户错误的信息,让用户决定如何去做。
  • CAS(乐观锁): 全称Compare And Swap,字面意思:“比较并交换"

线程不安全的解决方案:
1.加锁
2.ThreadLocal
3.Atomic*(乐观锁的实现)

/**
 * Atomic*乐观锁实现
 */
public class ThreadDemo91 {
    private static AtomicInteger count = new AtomicInteger(0);
    //最大循环次数
    private static final int MAXSIZE = 100000;

    public static void main(String[] args) throws InterruptedException {


        //i++
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAXSIZE ; i++) {
                 //count++;
                    count.getAndIncrement();
                }

            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAXSIZE ; i++) {
                    //count--;
                    count.getAndDecrement();//i--;
                }

            }
        });
        t2.start();

        t1.join();
        t2.join();

        System.out.println("结果"+count);
    }

在这里插入图片描述

题:CAS底层实现原理?
答:Java层面CAS的实现是unsafe类,unsafe类调用了C++的本地方法,通过调用操作系统的Atomic::cmpxchg(原子指令)来实现CAS操作的。

乐观锁性能比较高

CAS缺点:
存在ABA问题:AtomicInteger存在ABA问题(A预期旧值,B表示新值)
ABA问题统一的解决方案:增加版本号,每次修改之后更新版本号。
题: AtomicStampedReference和AtomicReference的区别:
答:AtomicStampedReference没有ABA问题 AtomicReference有ABA问题

/**
 * ABA问题解决演示
 * 核心是增加了版本号
 */
public class ThreadDemo94 {

    private static AtomicStampedReference money = new AtomicStampedReference(100,1);

    public static void main(String[] args) throws InterruptedException {

        //转账线程1(-100)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //第一次转账
             boolean result = money.compareAndSet(100,0,1,2);
                System.out.println("第一次转账(-100)"+result);
            }
        });
        t1.start();
        t1.join();

        //转入100
        Thread t3 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        boolean result = money.compareAndSet(0,100,2,3);
                        System.out.println("转入100(+100)"+result);
                    }
                });
                t3.start();
                t3.join();

        //转账线程2(-100)
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0,1,2);
                System.out.println("第二次转账(-100)"+result);
            }
        });
        t2.start();
    }
}

Integer高速缓存范围-128—127
设置应用程序的参数(-D),设置Integer高速缓存的最大值:
-Djava.lang.Integer.IntegerCache.high=1000
在这里插入图片描述

解决方案:调整integer的高速缓存边界值

悲观锁:认为通常情况下会出现并发冲突,所以在一开始就会进行加锁。 包含:synchronized

共享锁和非共享锁

共享锁:一把锁可以被多个线程所拥有
非共享锁(独占锁):一把锁只能被一个线程所拥有,synchronized

读写锁中的读锁就是共享锁
读写锁:将一把锁分成两个,一个用于读数据的锁(也叫做读锁)另一把所叫做写锁,读锁是可以被多个线程同时拥有的,写锁只能被一个线程拥有

读写锁的具体实现:ReentrantReadWriteLock

读写锁的优势:锁的粒度更加小,性能也更高

注意读写锁中读锁写锁是互斥的(防止同时读写所产生的脏数据

* 读写锁演示
 */
public class ThreadDemo96 {
    public static void main(String[] args) {

        //创建一个读写锁
        ReentrantReadWriteLock readWriteLock =
                new ReentrantReadWriteLock();//默认是非公平的
        //读锁
        ReentrantReadWriteLock.ReadLock readLock =
                readWriteLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock =
                readWriteLock.writeLock();

        //线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000));
        //任务1:执行读锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //加锁
                readLock.lock();
                try{
                    //业务逻辑处理
                    System.out.println("执行了读锁操作"+Thread.currentThread().getName()
                            + new Date());
                    Thread.sleep(3000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    readLock.unlock();
                }
            }
        });

        //任务2:执行读锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                readLock.lock();
                try{
                    //执行业务逻辑
                    System.out.println("执行了读锁操作"+Thread.currentThread().getName()+
                            new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    readLock.unlock();
                }
            }
        });

        //任务3:执行写锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try{
                System.out.println("执行了写锁操作"+Thread.currentThread().getName()+
                        new Date());
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
                }
        });

        //任务4:执行写锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try{
                    System.out.println("执行了写锁操作"+Thread.currentThread().getName()+
                            new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
            }
        });


    }
}

在这里插入图片描述

公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致 ,
new ReentrantLock(true)
非公平锁:锁的获取顺序和线程获取锁的先后顺序无关(默认锁策略),new ReentrantLock()/ new ReentrantLock(false)/synchronized
非公平锁优点:性能比较高 公平锁优点:执行是有序的,所以结果也是可预期的

  • 可重入锁:当一个线程获取一个锁之后,可以重复的进入
 * 可重入锁(synchronized*/
public class ThreadDemo97 {
    //创建锁 
    private static Object lock = new Object();

    public static void main(String[] args) {
        //第一次进入锁
        synchronized (lock){
            System.out.println("第一次进入所");
            synchronized (lock){
                System.out.println("第2次进入所");
            }

题:你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
答:1.乐观锁——》CAS——》Atomic* CAS是由V(内存值)A(预期旧值)B(新值)组成,执行的时候使用V==A对比,如果结果为true,表明没有并发冲突,可以直接修改,否则不能修改。CAS是通过调用C++实现提供的unsafe中的本地方法(Compare
And SwapXXX)来实现的,c++是通过调用操作系统Atomic::cmpxchg(原子指令)来实现的
2.悲观锁——》synchronized在Java中是将锁的ID存放到对象头来实现的,synchronized在JVM层面是通过监视器锁来实现,synchronized在操作系统层面是通过互斥锁mutex实现。

  • 自旋锁:通过死循环一直尝试获取锁

    自旋锁的缺点:如果发生死锁则会一直自 旋 (循环),所以会带来一定的额外开销。

synchronized锁优化(锁消除)

JDK1.6锁升级的过程:
(1) 无锁
(2) 偏向锁(第一个线程第一次访问)将线程ID存储在对象头中的偏向锁标识。
(3)轻量级锁(自旋)
(4) 重量级锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值