Java程序员必知的并发编程艺术——并发机制的底层原理实现

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。

volatile借助Java内存模型保证所有线程能够看到最新的值。(内存可见性)

实现原理:

将带有volatile变量操作的Java代码转换成汇编代码后,可以看到多了个lock前缀指令(X86平台CPU指令)。这个lock指令是关键,在多核处理器下实现两个重要操作:

1.将当前处理器缓存行的数据写回到系统内存。

2.这个写回内存的操作会使其他处理器里缓存该内存地址的数据失效

如果了解计算机组成原理,可以知道CPU为了提高处理速度,不和内存直接进行交互,而是使用Cache(高速缓存,通过缓存数据交互速度和内存不是一个数量级,而同时Cache的存储容量也很小)。

从内存将数据读到缓存后,CPU进行一系列数据操作,而操作完成时间是不可知的。而JVM对带有volatile变量进行写操作时,会发送Lock前缀指令,将数据从缓存行写入到内存。写入内存还不够,因为其他线程的缓存行中数据还是旧的,Lock指令可以让其他CPU通过监听在总线上的数据,检查自己的缓存数据是否过期,如果缓存行的地址和总线上的地址相同,则将缓存行失效,下次该线程对这个数据操作时,会重新从内存中读取,更新到缓存行。

2.Synchronized

Synchronized也是经常用到的,它给人的印象一般是”重量级锁”。在JDK1.6后,对Synchronized进行了一系列优化,引入了偏向锁和轻量级锁,对锁的存储结构和升级过程。有效减少获得锁和释放锁带来的性能消耗。

Synchronized同步基础:

1.普通同步方法,锁是当前实例对象。 public synchronized void test(){…}

2.静态同步方法,锁是当前类的Class对象。public static synchronized void test(…){}

3.对于同步方法块,锁是Synchronized括号中里配置的对象。synchronized(instance){…}

用javap反编译class文件,可以看到Synchronized用的是monitorenter和monitorexit实现加锁。一个monitorenter必须要有monitorexit与之对应,所以同步方法会在异常处和方法返回处加入monitorexit指令。

<pre style="box-sizing: border-box; margin: 0px 0px 24px; padding: 0px 16px; overflow-x: auto; background-color: rgb(255, 255, 255); font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; line-height: 20px; color: rgb(34, 34, 34); -webkit-tap-highlight-color: transparent; white-space: normal; text-align: start;">3: monitorenter //注意此处,进入同步方法 4: aload_0 5: dup 6: getfield #2 // Field i:I 9: iconst_110: iadd11: putfield #2 // Field i:I14: aload_115: monitorexit //注意此处,退出同步方法</pre>

3.Java对象头

Synchronized用到的锁存在Java对象头里,若对象非数组类型,用32bit存储(2个字宽,32虚拟机一个字宽为4字节,一个字节8bit)

MarkWord存储和锁相关的信息:

Java程序员必知的并发编程艺术——并发机制的底层原理实现

锁有四个等级: 无锁->偏向锁->轻量级锁->重量级锁。如果存在竞争,就会不断升级,但不会降级。

1.偏向锁

多数情况下,锁不会存在竞争,而是同一个线程多次获得。当某个线程访问同步块代码时,会将锁对象和栈帧中的锁记里存储锁偏向的线程ID,以后线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单比对一下对象头中的MarkWord里的线程ID,如果一致则表示线程获得锁。若不一致,再继续测试偏向锁的标识是否为1:如果没有设置(无锁状态),用CAS(Compare and Swap)竞争锁;如果设置了,尝试使用CAS将对象头的偏向锁指向当前线程。

当有另一个线程尝试竞争锁时,持有偏向锁的线程才会释放锁。需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word,要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

Java程序员必知的并发编程艺术——并发机制的底层原理实现

Java 6,7默认开启偏向锁,可以通过JVM的参数-XX:-UsebiasedLocking=false关闭

2.轻量级锁

(1)加锁

锁记录存储在栈桢,会将对象头的MarkWord复制到锁记录。线程在执行同步块时,会尝试用CAS将对象头的MarkWord替换为指向锁记录的指针,若成功,获得锁;失败表示其他线程竞争锁,当前线程尝试使用自旋获取锁。

(2)解锁

类似于加锁反向操作,会将锁记录复制会对象头的MarkWord。若成功,表示操作过程中没有竞争发生;若失败,存在竞争,锁会膨胀成重量级锁。

如下图:

Java程序员必知的并发编程艺术——并发机制的底层原理实现

当膨胀到重量级锁时,不会再通过自选获得锁(自旋时线程处于活动状态,会消耗CPU),而是将线程阻塞,获得锁的线程执行完后会释放重量级锁,此时唤醒因为锁阻塞的线程,进行新一轮的竞争。

3.其他锁概念

自旋锁:

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

<pre style="box-sizing: border-box; margin: 0px 0px 24px; padding: 0px 16px; overflow-x: auto; background-color: rgb(255, 255, 255); font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; line-height: 20px; color: rgb(34, 34, 34); -webkit-tap-highlight-color: transparent; white-space: normal; text-align: start;">public class SpinLock { private AtomicReference<Thread> sign =new AtomicReference<>(); public void lock(){Thread current = Thread.currentThread(); while(!sign .compareAndSet(null, current)){}} public void unlock (){Thread current = Thread.currentThread();sign .compareAndSet(current, null);}}</pre>

使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。

当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。

由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

锁的优缺点对比:


锁.png

分享一个多线程并发的学习思维导图:进群619881427可以免费获取大量架构师学习资料


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容