发表时间:2022-03-25来源:网络
synchronized有如下3种使用方式
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
当一个线程访问同步代码块时,需要获得锁才能执行,当退出或者抛出异常的时候要释放锁。那么是怎么实现的呢,我们来看一段代码
publicclassSynchronizedTest{ publicsynchronizedvoidtest1(){ } publicvoidtest2(){ synchronized(this){ } } }我们用javap来分析一下编译后的class文件,看看synchronized如何实现的

从这个截图上可以看出,同步代码块是使用monitorenter和monitorexit指令实现的, monitorenter插入到代码块开始的地方,monitorexit插入到代码块结束的地方,当monitor被持有后,就处于锁定状态,也就是上锁了。
下面我们深入分析一下synchronized实现锁的两个重要的概念:Java对象头和monitor
synchronized的锁是存在对象头里的,对象头由两部分数据组成:Mark Word(标记字段)、Klass Pointer(类型指针)
Mark Word存储了对象自身运行时数据,如hashcode、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID等等。是实现轻量级锁和偏向锁的关键,Klass Pointer是Java对象指向类元数据的指针,jvm通过这个指针确定这个对象是哪个类的实例
每个Java对象从娘胎里出来就带着一把看不见的锁, 叫做内部锁或者monitor锁,我们可以把它理解成一种同步机制,它是线程私有的数据结构,
FIFO结构图在AQS中维护着一个上面的FIFO的同步队列,当线程获取同步状态失败后,则会加入到这个CLH同步队列的对尾并一直保持着自旋。
在CLH同步队列中的线程在自旋时会判断其前驱节点是否为首节点,如果为首节点则不断尝试获取同步状态sate,获取成功则退出CLH同步队列。当线程执行完逻辑后,会释放同步状态sate,释放后会唤醒其后继节点。
tryAcquire方法尝试去获取锁,获取成功返回true,否则返回false。该方法由继承AQS的子类自己实现。采用了模板方法设计模式。
如:ReentrantLock的Sync内部类,Sync的子类:NonfairSync和
1、独占 ( Exclusive ):只有一个线程能执行,其原理是看哪个线程先把state +1 ,谁就抢到了锁,如 ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的,所以非公平锁效率较高
2、共享 ( Share ):多个线程可同时执行,其原理就是多个线程去操作state字段,来一个线程就 +1,来一个线程就 +1
线程运行结束后state -1 ,一直减到0,就释放锁了。如Semaphore、CountDownLatch。
很多小伙伴认为ThreadLocal是多线程同步机制的一种,其实不然,他是为多线程环境下为变量线程安全提供的一种解决思路,他是解决多线程下成员变量的安全问题,不是解决多线程下共享变量的安全问题。
线程同步机制是多个线程共享一个变量,而ThreadLocal是每个线程创建一个自己的单独变量副本,所以每个线程都可以独立的改变自己的变量副本。并且不会影响其他线程的变量副本。
ThreadLocal内部有一个非常重要的内部类:ThreadLocalMap,该类才是真正实现线程隔离机制的关键,ThreadLocalMap内部结构类似于map,由键值对key和value组成一个Entry,key为ThreadLocal本身,value是对应的线程变量副本
注意:
1、ThreadLocal本身不存储值,他只是提供一个查找到值的key给你。
2、ThreadLocal包含在Thread中,不是Thread包含在ThreadLocal中。
ThreadLocalMap 和HashMap的功能类似,但是实现上却有很大的不同:
HashMap 的数据结构是数组+链表
ThreadLocalMap的数据结构仅仅是数组
HashMap 是通过链地址法解决hash 冲突的问题
ThreadLocalMap 是通过开放地址法来解决hash 冲突的问题
HashMap 里面的Entry 内部类的引用都是强引用
ThreadLocalMap里面的Entry 内部类中的key 是弱引用,value 是强引用
这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。
这种方法的基本思想是一旦发生了冲突,就去寻找下一个空的散列地址(这非常重要,源码都是根据这个特性,必须理解这里才能往下走),只要散列表足够大,空的散列地址总能找到,并将记录存入。
开放地址法:
容易产生堆积问题,不适于大规模的数据存储。
散列函数的设计对冲突会有很大的影响,插入时可能会出现多次冲突的现象。
删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂。
链地址法:
处理冲突简单,且无堆积现象,平均查找长度短。
链表中的结点是动态申请的,适合构造表不能确定长度的情况。
删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间。
ThreadLocal 中看到一个属性 HASH_INCREMENT = 0x61c88647 ,0x61c88647 是一个神奇的数字,让哈希码能均匀的分布在2的N次方的数组里, 即 Entry[] table,关于这个神奇的数字google 有很多解析,这里就不重复说了
ThreadLocal 往往存放的数据量不会特别大(而且key 是弱引用又会被垃圾回收,及时让数据量更小),这个时候开放地址法简单的结构会显得更省空间,同时数组的查询效率也是非常高,加上第一点的保障,冲突概率也低



从上面的结构图,我们已经窥见ThreadLocal的核心机制:
每个Thread线程内部都有一个Map。Map里面存储线程本地对象(key)和线程的变量副本(value)Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,彼此之间互不干扰。
皓盘云建最新版下载v9.0 安卓版
53.38MB |商务办公
ris云客移动销售系统最新版下载v1.1.25 安卓手机版
42.71M |商务办公
粤语翻译帮app下载v1.1.1 安卓版
60.01MB |生活服务
人生笔记app官方版下载v1.19.4 安卓版
125.88MB |系统工具
萝卜笔记app下载v1.1.6 安卓版
46.29MB |生活服务
贯联商户端app下载v6.1.8 安卓版
12.54MB |商务办公
jotmo笔记app下载v2.30.0 安卓版
50.06MB |系统工具
鑫钜出行共享汽车app下载v1.5.2
44.7M |生活服务
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-02-15
2022-02-14