常见锁bing

Posted 鸟随二月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见锁bing相关的知识,希望对你有一定的参考价值。

上一篇

锁策略

乐观锁

它认为一般情况下不会出现问题,所以他在使用的时候不会加锁,只有在数据修改的时候,才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返回失败信息给用户自行处理。

乐观锁的经典事项:CAS (Compare And Swap)对比并且替换。
CAS 实现:(v【内存中值】,A【预期的旧值】,B_(新值))
V==A 对比? true ->V->B; false->不能修改
在这里插入图片描述

CAS原理

CAS在 Java中是通过UnSafe 实现,Unsafe本地类和本地方法,它是C/C++实现的原生方法,通过调用操作系统Atomic::cmpxchg(原子指令)来实现的。
在这里插入图片描述

CAS在 Java中的应用: AtomicInteger/Atomic*

在多线程中实现i++、i–保证线程安全的方法:
1.加锁
2.ThreadLocal
3.AtomicInteger
在这里插入图片描述

 private static AtomicInteger count =
            new AtomicInteger(0);
    private static final int MAXSIZE = 100000;

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

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

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

        t1.join();
        t2.join();
        System.out.println("最终结果:" + count);
    }

乐观锁(CSA)问题解决及方案

问题

ABA问题
代码模拟:

  private static AtomicReference money =
            new AtomicReference(100);

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

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

        t1.join();

        // 账户增加了 100
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(0, 100);
                System.out.println("线程3转入100元:" + result);
            }
        });
        t3.start();


        t3.join();

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

    }

解决方案

使用版本号,每次修改的时候判断预期的旧值和版本号,每次成功修改之后更改版本号,这样即使预期的值和Ⅳ值相等,但因为版本号不同,所以也不能进行修改,从而解决了ABA的问题。
解决方案:AtomicStampedReference
在这里插入图片描述
代码解决:

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

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

        // 转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100, 0,
                        1, 2);
                System.out.println("线程1执行转账:" + 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("线程3转入100元:" + result);
            }
        });
        t3.start();


        t3.join();

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


    }

AtomicStampedReference注意事项

在这里插入图片描述

解决方案

在这里插入图片描述

悲观锁

悲观锁:悲观锁任务只要执行多线程就会出现问题,所以在进入方法之后会直接加锁。
悲观锁的实现: synchronized

synchronized实现

在 Java层面:是将锁标识放在对象头(每个对象都有对象头)中;
在JVM 层面是监视器锁;
在操作系统是互斥锁mutex。

synchronized jdk1.7优化?

无锁->偏向锁->轻量级锁(自旋)->重量级锁。

公平锁和非公平锁

公平锁:获取锁的顺序按照线程访问的先后顺序获取。
公平锁非公平锁:不会按照线程的先后访问顺序按需获取。
Java实现:
new ReentrantLock(true)

独占锁和共享锁

独占锁:指的是这一把锁只能被一个线程拥有(例如:synchronized)。
共享锁:指的是一把锁可以被多个线程同时拥有。(例如:ReadWriterLock 读写锁,读锁就是共享。优势:将锁的粒度更加的细化,从而提高锁的性能。)
读写锁例子:

   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));

        // 任务一:读锁演示
        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();
                }
            }
        });

        // 任务二:读锁演示
        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();
                }
            }
        });

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

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

    }

读锁是共享锁,写锁是独占锁,,读锁和写锁是非共享的。

可重入锁

一个线程在拥有了一把锁之后,可以重复的进入,就叫做可重入锁。
可重入锁的经典代表: synchronized
在这里插入图片描述典型使用场景:
synchronized、ReentrantLock

自旋锁

相当于死循环,一直循环尝试获取锁。
在这里插入图片描述
使用场景:synchronized

偏向锁

在线程初次访问的时候,将线程的ID放到对象头偏向锁ID的字段中,每次线程访问时判断一下线程的id是否等于对象头中的偏向锁id,如果相等则表明这个线程拥有此锁就可以正常执行代码;否则表明线程不拥有此锁,只能通过自旋的方式尝试获取锁。
下一篇

以上是关于常见锁bing的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal单例模式线程通讯bing

ThreadLocal单例模式线程通讯bing

常见的代码片段

Alfred常见使用

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段

为啥基于锁的程序不能组成正确的线程安全片段?