• 【Java多线程】synchronized、ReentrantLock基础原理 Hi


    什么是多线程?

    在执行代码的过程中,我们很多时候需要同时执行一些操作,这些同时进行操作可以尽可能的提升代码执行效率,充分发挥CPU运算能力。

    public class Test implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + "_" + i);
            }
        }
    
        public static void main(String[] args) {
            Test t1 = new Test();
            Thread ta = new Thread(t1, "tbA");
            Thread tb = new Thread(t1, "tbB");
            ta.start();
            tb.start();
        }
    }
    
    

    输出:

    tbA_0
    tbB_0
    tbA_1
    tbB_1
    tbB_2
    tbA_2
    
    

    为什么使用多线程?

    • 分离单一逻辑
    • 提高代码效率
    • 充分发挥硬件能力

    多线程的代价?

    在多个线程同时需要访问同一对象时,会出现意料之外的结果。

    public class Test implements Runnable {
        private static Integer i = 0;
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "的赛道:" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            Test t1 = new Test();
            System.out.println("----4位选手进入赛道----");
            Thread thread1 = new Thread(t1, "tbA");
            Thread thread2 = new Thread(t1, "tbB");
            Thread thread3 = new Thread(t1, "tbC");
            Thread thread4 = new Thread(t1, "tbD");
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
        }
    }
    

    输出:

    ----4位选手进入赛道----
    tbA的赛道:1
    tbD的赛道:3
    tbB的赛道:1
    tbC的赛道:2
    

    选手A和B进入了同一赛道,这不是我们期望的。

    怎么保证线程安全?

    为对象加锁,可以最大可能保证线程安全。
    线程锁有哪些?
    最常用的是synchronized,除此之外还有ReentrantLock

    synchronized锁原理

    两个概念

    CAS

    是一个CPU指令,三个参数:地址,原始值,新值,返回一个bool型。

    function cas(p , old , new ) returns bool {
        if *p ≠ old { // *p 表示指针p所指向的内存地址
            return false
        }
        *p ← new
        return true
    }
    
    

    Mark Word

    Java对象头的Mark Word中存储了HashCode、分代年龄、锁状态等信息。

    三种锁

    三种锁依次完成了三种设想下的线程安全保障。

    起初我们悲观的认为,几乎所有的多线程访问对象都可能存在并发竞争,需要阻塞竞争的线程以已达到在同一时间只有一个线程访问对象。这就是synchronized最初设计的重量级锁。

    重量级锁

    使用操作系统的互斥量实现。在用户态与内核态切换,需要消耗较大性能。

    内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
    用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

    轻量级锁

    轻量级锁的诞生源于我们的比较乐观的设想——部分多线程在访问对象时可能是串行的竞争关系。
    串行竞争是指,虽然多线程依然存在竞争,但不是同时访问,而是依次的。我们可以让线程按照先来后到的顺序有序访问。

    在这里插入图片描述

    流程:
    1.对象(OBJ)被创建,它目前没有被任何线程占用
    2.线程1尝试访问OBJ,通过OBJ的头标记(Mark Word)内记录的信息,发现他没有被任何线程占用
    3.线程1将OBJ的Mark Word拷贝至自己栈帧的锁空间(Lock Record)内,我们称它为置换标记(Displaced Mark Word),并通过CAS将原OBJ的Mark Word内所指针指向线程1的Lock Record
    4.线程1开始占用OBJ,并执行自己内部操作
    5.线程2尝试访问OBJ,通过OBJ的Mark Word发现已被线程1占用
    6.线程2开始执行自旋锁,进入循环等待,每隔一段时间尝试通过CAS判断OBJ是否被占用,如果是则继续循环等待,如果否则占用OBJ,自旋尝试有次数限制(默认10,可以通过JVM调优修改)
    7.线程1执行完毕,通过CAS将自己Displaced Mark Word拷贝至 OBJ的Mark Wrod,进行复原,释放OBJ
    8.线程2在自旋锁执行过程中发现OBJ已经被线程1释放,执行第3步操作占用OBJ
    9.线程3尝试访问OBJ...

    偏向锁

    轻量级锁已经极大优化了重量级锁阻塞带来的负担。但很快,我们又想到另一种更乐观的多线程情况——只有一个线程多次访问某个对象。
    在这种情况下,甚至没有第二个线程,只有唯一的一个线程不断的访问对象。不存在其他线程竞争,不存在等待。
    在这里插入图片描述

    流程:
    1.线程1尝试访问OBJ,通过其头标记(Mark Word)发现其没有线程占用且可以设置偏向锁(Mark Wor中有一个标识代表是否可以设置偏向锁)
    2.线程1通过CAS占用OBJ,并执行自己的操作
    3.线程1再次尝试访问OBJ,发现Mark Word中记录的是自己的信息,则直接访问OBJ执行操作

    锁之间的关系

    轻量级锁及偏向锁是较乐观的情况,但如果出现了不那么乐观的特殊情况怎么办?

    一般synchronized锁会默认执行偏向锁,但在执行过程中发现有其他线程竞争,自动膨胀至轻量级锁。但当执行多次自旋锁都没法争取对象时,将自动膨胀至重量级锁。

    在这里插入图片描述

    ReentrantLock锁原理

    ReentrantLock的原理是通过CAS及AQS队列搭配实现。

    AQS
    AQS使用一个FIFO的队列(也叫CLH队列,是CLH锁的一种变形),表示排队等待锁的线程。队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。

    流程:
    1.线程1尝试访问OBJ,通过AQS队列的state属性发现其没有被占用(state=0)
    2.线程1占用OBJ,设置AQS的state=1,并设置其AQS的thread为当前线程(线程1)
    3.线程2尝试访问OBJ。发现其已被占用(状态为1),加入AQS的等待队列
    4.线程1执行完毕,释放OBJ
    5.位于AQS等待队列最前的线程2开始尝试访问OBJ...

    代码

    了解了锁的原理,让我们用实际代码解决刚开始遇到的多线程问题。

    synchronized方式:

    public class Test implements Runnable {
        private static Integer i = 0;
        @Override
        public void run() {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + "的赛道:" + (++i));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            Test t1 = new Test();
            System.out.println("----4位选手进入赛道----");
            Thread thread1 = new Thread(t1, "tbA");
            Thread thread2 = new Thread(t1, "tbB");
            Thread thread3 = new Thread(t1, "tbC");
            Thread thread4 = new Thread(t1, "tbD");
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
        }
    }
    
    

    输出:

    ----4位选手进入赛道----
    tbA的赛道:1
    tbD的赛道:2
    tbB的赛道:3
    tbC的赛道:4
    

    ReentrantLock方式:

    public class Test implements Runnable {
        private static Integer i = 0;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "的赛道:" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    
        public static void main(String[] args) {
            Test t1 = new Test();
            System.out.println("----4位选手进入赛道----");
            Thread thread1 = new Thread(t1, "tbA");
            Thread thread2 = new Thread(t1, "tbB");
            Thread thread3 = new Thread(t1, "tbC");
            Thread thread4 = new Thread(t1, "tbD");
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
        }
    }
    

    输出与上一个一致。

    参考文献: https://www.cnblogs.com/maxigang/p/9041080.html
    https://blog.csdn.net/lengxiao1993/article/details/81568130
    https://zhuanlan.zhihu.com/p/249147493

  • 相关阅读:
    C#委托
    资源推荐 五个常用MySQL图形化管理工具
    C#数组
    虚方法与多态
    How to connect to MySQL database from Visual Studio VS2010 – problems with NET connectors
    2021秋软工实践第二次结对编程作业
    2021秋软工实践第一次个人编程作业
    低维数据可视化
    2021秋季软件工程实践总结
    2021秋软工实践第一次结对编程作业
  • 原文地址:https://www.cnblogs.com/JHelius/p/16316766.html
Copyright © 2020-2023  润新知