首页 > 编程语言 >还没有一个人能够把并发编程讲解的这么透彻

还没有一个人能够把并发编程讲解的这么透彻

时间:2023-06-12 12:02:28浏览次数:43  
标签:同步 monitor 标记 对象 编程 并发 ThreadLocal 透彻 线程

● Synchronization的底层实现概述

 Java虚拟机的同步(Synchronized)是基于进入和退出管理对象(monitor)实现的。同步方法并不是由monitor enter和monitor exit指令来实现同步的,而是由方法调用指令读取运行时常量池中的方法的ACC_SYNCHRONIZED标志来隐式实现的。

 注:monitor enter 和 monitor exit 指令是C语言的内容。

● 对象的内存模型

 

 

● 对象组成介绍

对象头:存储对象的 hashCode、锁信息或分代年龄或 GC 标志,类型指针指向对象的类元数据,JVM 通过这个指针确定该对象是哪个类的实例等信息。

实例变量:存放类的属性数据信息,包括父类的属性信息。

填充数据:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

● 加锁时的流程

当执行 synchronized 同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁)的起始地址来记录锁信息。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

另外的线程想获取对象头中的锁信息的时候,会发现对象头中已经记录一把锁(monitor),他就获取不到。monitor是互斥的,对象头记录的monitor就不会分配给其他线程了,此时这个线程就会进入阻塞状态。当执行中的线程发生异常,或者是释放锁标记,对象头的锁信息就会释放它记录的monitor。阻塞状态的线程就会弹出来一同争夺,重新在锁信息中记录monitor。

ObjectMonitor(java的monitor的实现)中有两个队列,_WaitSet 和 _EntryList,以及_Owner标记。其中WaitSet是用于管理等待队列(wait)线程的,EntryList 是用于管理锁池阻塞线程的,_Owner 标记用于记录当前执行线程。

● 线程状态流程

 

当多线程并发访问同一个同步代码时,首先会进入EntryList,当线程获取锁标记后,monitor 中的Owner 记录此线程,并在 monitor 中的计数器执行递增计算(+1),代表锁定,其他线程在EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算,并将Owner 标记赋值为 null,代表放弃锁,执行线程进入WaitSet 中阻塞。若执行线程调用 notify/notifyAll 方法,WaitSet 中的线程被唤醒,进入EntryList 中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor 中的Owner标记赋值为 null,且计数器赋值为 0 计算。

● 什么是锁的重入

在 Java 中,同步锁是可以重入的。只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可重入。当线程持有锁时,会在 monitor 的计数器中执行递增计算,若当前线程调用其他同步代码,且同步代码的锁对象相同时,monitor 中的计数器继续递增。每个同步代码执行结束,monitor 中的计数器都会递减,直至所有同步代码执行结束,monitor 中的计数器为 0 时,释放锁标记,_Owner 标记赋值为 null。

ThreadLocal介绍

ThreadLocal叫做线程本地变量,也有些地方叫做线程本地存储,ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

● Thread与ThreadLocal源码的解析

public void set(T value) {
    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)
        map.set(this, value);    else        createMap(t, value);}

ThreadLocalMap getMap(Thread t) { return t.threadLocals;}void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}

我们以set方法为例子,当线程第一次调用ThreadLocal的set方法时候会创建Thread对象里面的threadLocals成员。也就是说ThreadLocal的每次set和get操作,都是对Thread的threadLocals进行操作,ThreadLocals类似于threadLocals变量的管理者。

threadLocals类似一个特殊的Map,它的key就是threadLocal的实例,而value就是我们设置的value值。

● ThreadLocal使用注意事项

在一个操作系统中,线程和进程是有数量上限的。在操作系统中,确定线程和进程唯一性的唯一条件就是线程或进程 ID。操作系统在回收线程或进程的时候,不是一定杀死线程或进程,在繁忙的时候对线程或进程栈数据的操作,可能重复使用线程或进程。

使用ThreadLocal的时候,一定注意回收资源问题,每个线程结束之前,将当前线程保存的线程变量一定要删除 ,调用ThreadLocal.remove(),要不会发生泄露。run方法的finally代码块。

在并发量高的时候,可能有内存溢出。

● ThreadLocal使用实例

public class Test_ThreadLocal {    volatile static String name = "zhangsan";    static ThreadLocal<String> tl = new ThreadLocal<>();    public static void main(String[] args) {        new Thread(new Runnable() {            @Override            public void run() {                try {                    TimeUnit.SECONDS.sleep(1);                } catch (InterruptedException e) {                    e.printStackTrace();                }                name = "lisi";                tl.set("wangwu");            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                try {                    // 等待前一条线程运行结束                    TimeUnit.SECONDS.sleep(3);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(name);                System.out.println(tl.get());            }        }).start();    }}

运行结果是:lisi 和 null。

就是说虽然第二条线程的打印在第一条线程set之后,当时它在ThreadLocad容器中取不到相关的值。

锁的种类

Java 中锁的种类包括偏向锁,自旋锁,轻量级锁,重量级锁。锁的使用方式先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足,升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。锁只能升级,不能降级。

偏向锁

偏向锁是一种编译解释锁。如果代码中不可能出现多线程并发争抢同一个锁的时候,JVM 编译代码,解释执行的时候,会自动的放弃同步信息。消除 synchronized 的同步代码结果。可以避免锁的争抢和锁池状态的维护,提高JVM解释效率。

Object o = new Object();public void m() {    o = new Object();    synchronized (o) {    }}

 

轻量级锁

当偏向锁不满足,也就是有多线程并发访问,锁定同一个对象的时候,先提升为轻量级锁。也是使用标记 ACC_SYNCHRONIZED 标记记录的。ACC_UNSYNCHRONIZED 标记记录未获取到锁信息的线程。就是只有两个线程争抢锁标记的时候,优先使用轻量级锁。A线程和monitor有直接关联的。B线程不记录monitor,是monitor记录B线程,线程A结束后,B两个线程才找到monitor。但也可能有时候还是会出现重量级锁。

自旋锁

自旋锁是一个过渡锁,是偏向锁和轻量级锁的过渡。当获取锁的过程中,未获取到。为了提高效率,JVM 自动执行若干次空循环,再次申请锁,而不是进入阻塞状态的情况。故称为自旋锁。自旋锁提高效率就是避免线程状态的变更。

重量级锁

自旋锁不会一直持续自旋下去,当自旋一定次数的时候,还没获取到锁就会进入阻塞,该锁膨胀为重量级锁。重量级会让其他申请线程阻塞,性能降低。



标签:同步,monitor,标记,对象,编程,并发,ThreadLocal,透彻,线程
From: https://blog.51cto.com/u_14347868/6461300

相关文章

  • stm32 74HC4051 编程实现
    4个引脚,具有8通道模拟/数字多路复用器(MUX)功能 通过对S0、S1、S2置对应值,选择对应通道 再配置对应的PA6UI1-8对应的1个引脚为ADC方式   参考链接:https://controllerstech.com/multiplexer-74hc4051-and-stm32/......
  • 实验6 turtle绘图与python库应用编程体验
    实验任务1task1-11fromturtleimport*2defmove(x,y):3penup()4goto(x,y)5pendown()6defdraw(n,size=100):7foriinrange(n):8fd(size)9left(360/n)10defmain():11pensize(2)12pencolor(......
  • 编程名词
    .NETCLI:命令行接口(CLI)工具,用于开发,生成,运行和发布.NET应用程序的跨平台工具链。.NETSDK:用于开发、生成和测试应用的工具、库和运行时集。.NETCLR(运行时):用于运行应用程序的运行时和库集。IL:中间语言,是一种紧凑型的代码格式,可在任何操作系统或体系结构收到支持。......
  • 实验7 面向对象编程与内置模块
    实验任务1:实验源码:1classAccount:2"""一个模拟银行账户的简单类"""34def__init__(self,name,account_number,initial_amount=10):5"""构造新账户"""6self._name=name7......
  • 实验6 turtle绘图与python库应用编程体验
    task1-1源代码1fromturtleimport*23defmove(x,y):4'''画笔移动到坐标(x,y)处'''5penup()6goto(x,y)7pendown()89defdraw(n,size=100):10'''绘制边长为size的正n变形'''1......
  • C语言编程—递归
    递归指的是在函数的定义中使用函数自身的方法。举个例子:从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?"从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?'从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……'"语......
  • 实验7 面向对象编程与内置模块
    实验1task1.py实验源码:classAccount:'''一个模拟银行账户的简单类'''def__init__(self,name,account_number,initial_amount=10):'''构造新账户'''self._name=nameself._card_no=......
  • 实验6 turtle绘图与Python库应用编程体验
    task1-1.py实验源码:fromturtleimport*defmove(x,y):penup()goto(x,y)pendown()defdraw(n,size=100):foriinrange(n):fd(size)left(360/n)defmain():pensize(2)pencolor('red')move(-200,0)......
  • NOI / 1.9编程基础之顺序查找
    06:笨小猴描述笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!这种方法的具体描述如下:假设maxn是单词中出现次数最多的字母的出现次数,minn是单词中出现次数最少的字母的出现次数,如果maxn-mi......
  • 面向切面编程AOP之深入了解
    1. 先导  103IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能,把它转化成组件。AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。(AOP是一种编程技术)AOP是对OOP的补充延伸。AOP底层使用的就是动态代理来实现的。Spring的AOP使用的动态代理是:JDK动态代理 +......