【JUC并发编程】synchronized原理分析(上)(偏向锁/轻量级锁/重量级锁/手写重入锁)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 【JUC并发编程】synchronized原理分析(上)(偏向锁/轻量级锁/重量级锁/手写重入锁),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、synchronized前置知识

  1. 偏向锁→轻量锁(cas自旋)→重量级锁
  2. 重入锁概念/悲观锁与乐观锁
  3. 基于CAS手写-重入锁
  4. 基于CAS手写类似synchronized锁的升级过程

补充概念:
偏向锁: 同一个线程在没有其他线程竞争锁的情况下,可以一直复用我们的锁,不会重复获取锁。
轻量锁: 多个线程同时获取锁,只有一个线程获取锁,没有获取到锁的线程会通过CAS不断重试(可以配置重试次数)
重量级锁: 重试多次如果没有获取到锁,则当前线程会变成阻塞—-用户态
切换到内核态/上下文切换

CAS
优点:不需要用户态切换内核态 一直在我们用户空间中自旋。
缺点: 有可能会非常的消耗到cpu资源。


二、重入锁

重入锁:当前线程如果已经获取到锁,则不会重复的获取锁,而是直接复用。
我们的synchronized/lock

锁如果不具有重入性,当前线程递归调用方法中有可能会发生死锁的问题。

1. 演示重入锁

public class Test01 {
    private static DemoLock demoLock = new DemoLock();

    public static void main(String[] args) {
        a();
    }

    //    public static synchronized void a() {
//        System.out.println("a");
//        b();
//    }
//
//    public static synchronized void b() {
//        System.out.println("b");
//        c();
//    }
//
//    public static synchronized void c() {
//        System.out.println("c");
//    }
    public static void a() {
        demoLock.lock();
        System.out.println("a");
        b();
        demoLock.lock();
    }

    public static void b() {
        demoLock.lock();
        System.out.println("b");
        c();
        demoLock.lock();
    }

    public static void c() {
        demoLock.lock();
        System.out.println("c");
        demoLock.lock();
    }

}

2. 改造重入锁代码

public class DemoLock {
    /**
     * lockState===0没有任何线程获取到该锁
     * lockState===1已经有线程获取到该锁
     */
    private AtomicInteger lockState = new AtomicInteger(0);
    /**
     * 当前谁持有了该锁
     */
    private Thread ownerLockThread;
    /**
     * 记录锁的重入次数
     */
    private int recursions;

    /**
     *
     */
    public void lock() {
        if (ownerLockThread != null && ownerLockThread == Thread.currentThread()) {
            // 重入次数+1
            recursions++;
            return;
        }
        /**
         * 两个线程同时执行 cas 操作 将锁的状态从0改成===11
         * 最终只有一个线程修改成功 自旋
         */
        for (; ; ) {
            if (lockState.compareAndSet(0, 1)) {
                ownerLockThread = Thread.currentThread();
                recursions++;
                return;
            }
            // 获取失败 重试获取锁
        }

    }

    public void unLock() throws Exception {
        /**
         *  如果不是当前线程获取的锁 无法释放锁
         */
        if (ownerLockThread != Thread.currentThread()) {
            throw new Exception("当前线程没有获取到锁无法释放锁");
        }
        recursions--;
        if (recursions == 0) {
            for (; ; ) {
                // 释放锁 需要通过 cas 将 11 == 变成0
                if (lockState.compareAndSet(1, 0)) {
                    return;
                }
            }
        }


    }

    public static void main(String[] args) throws InterruptedException {
        DemoLock demoLock = new DemoLock();
        // 当前主线程调用lock方法
        demoLock.lock();
        demoLock.lock();
    }
}


三、轻量级改造重量级锁

在这里插入图片描述
Lock锁属于轻量级吗?
Lock 执行cas 竞争 —-属于量级锁 如果多次获取锁(自旋失败),为了避免消耗cpu的,将该线程变为阻塞状态,就会变为重量级锁。
应用场景:

public class DemoLock {
    /**
     * lockState===0没有任何线程获取到该锁
     * lockState===1已经有线程获取到该锁
     */
    private AtomicInteger lockState = new AtomicInteger(0);
    /**
     * 当前谁持有了该锁
     */
    private Thread ownerLockThread;
    /**
     * 记录锁的重入次数
     */
    private int recursions;


    /**
     * 没有获取到锁的线程
     */
    private LinkedBlockingDeque<Thread> notLockThreadList = new LinkedBlockingDeque<>();

    /**
     *
     */
    public void lock() {
        if (ownerLockThread != null && ownerLockThread == Thread.currentThread()) {
            // 重入次数+1
            recursions++;
            return;
        }
        /**
         * 两个线程同时执行 cas 操作 将锁的状态从0改成===11
         * 最终只有一个线程修改成功 自旋
         */
        int spinCount = 0;
        for (; ; ) {
            // 如果自旋次数超过10次+
            if (spinCount > 10) {
                // 当前线程直接阻塞 该过程为重量级锁
                notLockThreadList.offer(Thread.currentThread());
                LockSupport.park();
            }
            if (lockState.compareAndSet(0, 1)) {
                ownerLockThread = Thread.currentThread();
                recursions++;
                return;
            }
            // 自旋次数+1
            spinCount++;


        }

    }

    public void unLock() throws Exception {
        /**
         *  如果不是当前线程获取的锁 无法释放锁
         */
        if (ownerLockThread != Thread.currentThread()) {
            throw new Exception("当前线程没有获取到锁无法释放锁");
        }
        recursions--;
        if (recursions == 0) {
            for (; ; ) {
                // 释放锁 需要通过 cas 将 11 == 变成0
                if (lockState.compareAndSet(1, 0)) {
                    // 唤醒正在阻塞的线程, 进入到获取锁的状态
                    notifyNotLockThread();
                    return;
                }
            }
        }


    }

    private void notifyNotLockThread() {
        /**
         * 唤醒所有的线程  非公平锁的形式
         */
        notLockThreadList.forEach((t) -> {
            LockSupport.unpark(t);
        });
    }

    public static void main(String[] args) throws Exception {
        cal();
    }

    public static void cal() throws Exception {
        DemoLock demoLock = new DemoLock();
        try {
            demoLock.lock();
            new Thread(() -> {
                System.out.println("子线程获取锁开始>");
                demoLock.lock();
                System.out.println("子线程获取锁结束>");
            }).start();
            System.out.println("模拟主线程正在处理逻辑开始");
            Thread.sleep(500);
            System.out.println("模拟主线程正在处理逻辑结束");
        } catch (Exception e) {

        } finally {
            demoLock.unLock();
        }
    }
}

四、公平锁与非公平锁

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

比如当前 abcd 四个线程,假设a线程获取到锁,在释放锁时:

  • 公平锁:
    根据bcd 请求锁的顺序排列获取锁,B先获取锁那这把锁就给我们的线程B。
  • 非公平锁:
    bcd 同时竞争这把锁;谁能够抢成功 谁就获取锁成功。

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

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

public class Thread002 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 DemoLock()).start();
        }
    }
}


五、偏向锁/轻量级锁/重量级锁应用场景

Synchronized 锁的升级过程
t1 线程 获取到锁 1毫秒就释放锁,t1线程1毫秒之后又唤醒t2线程竞争锁资源
t2竞争锁资源 阻塞—就绪—cpu调度—竞争锁资源
锁的升级过程。

t1 线程获取到锁之后 60s 才会释放锁,t2线程自旋60s t2直接阻塞就可以–

1.偏向锁:加锁和解锁不需要额外的开销,只适合于同一个线程访问同步代码块,无需额外的开销,如果多个线程同时竞争的时候,会撤销该锁。
2.轻量级锁:竞争的线程不会阻塞,提高了程序响应速度,如果始终得不到锁的竞争线程,则使用自旋的形式,消耗cpu资源,适合于同步代码块执行非常快的情况下,自旋(jdk1.7以后智能自转)
3.重量级锁: 线程的竞争不会使用自旋,不会消耗cpu资源,适合于同步代码执行比较长的时间。

偏向锁与重入锁之间有什么区别?
偏向锁与重入锁实现的思路基本上相同

偏向锁:当前只有一个线程的情况下,没有其他的线程竞争锁,该锁一直会被我们的该线程持有,不会立即释放锁;有另外的一个线程竞争该锁,就会撤销我们的偏向锁 线程id

重入锁:当前线程如果已经获取到了锁,当前线程中的方法如果有需要获取锁的话,则直接复用。
释放锁,判断重入的次数,如果为0, cas 改状态(1,0)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131218.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!