java并发

1.java对象头

    java对象头包含两个部分,一个指向对象类型的指针,一个MarkWord存放(对象的hashcode,分代年龄,以及锁相关的信息如:锁状态,锁标志位,是否是偏向锁等),如果该对象是数组,还会有一个数组长度

2.Synchronized(锁升级的过程)

    1.三种使用方式

            - 修饰方法 锁是当前实例对象

            - 修饰静态方法 锁是当前类的class对象

            - 修饰代码块 锁是synchronized括号里面的对象

    2.无状态锁,偏向锁,轻量级锁,重量级锁

        - 偏向锁

         HotSpot作者发现,在大部分的时候,锁不仅不存在被多个对象竞争,反而被同一个线程所一直持有,如果同一个线程一直获得锁和释放锁就造成了(上下文切换)资源的浪费。所以引入了偏向锁,当锁是偏向锁状态时,对象中markword中有一个线程ID,该线程id指向持有该偏向锁的线程,每次获得该锁之前,只需判断该线程ID是不是请求的线程,如果是的,则证明当前线程持有锁则不需要重新获取锁。

    偏向锁初始化,当对象想获取偏向锁时,会使用CAS操作将MarkWrod中的线程id指向栈帧中锁记录得线程id,如果CAS失败则证明还有其他线程竞争资源,则可能转变成轻量级锁。

    偏向锁的释放,不是在代码块执行完之后释放,而是如果有其他线程过来竞争,才会释放,这个时候由safepoint 判断当前线程是否处于不活动状态,如果处于不活动状态则释放锁。

    偏向锁的关闭需要手动设置一个参数-XX:BiasedLockingStartupDelay=0

偏向锁 -> 轻量级锁

    而是在偏向锁的获取过程中, 发现了竞争时, 直接将一个被偏向的对象“升级到” 被加了轻量级锁的状态。 这个操作的具体完成方式如下:

    首先通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 暂停拥有偏向锁的线程,然后在该线程的栈帧中补充上轻量级加锁时, 会保存的锁记录(Lock Record), 然后将被获取了偏向锁对象的 MarkWord 更新为指向这条锁记录的指针。最后唤醒暂停线程


    - 轻量级锁

    线程在执行代码同步快时,会在线程的栈帧中创建用于存储锁记录的空间,并将对象的markWord 复制过去,称为Replace markWord ,然后采用CAS的方法,讲对象头下·中的markWord替换成指向锁记录的指针,如果替换成功则持有锁,如果替换失败,则采用自旋的方式获得锁,如果自旋(一定次数)获得锁失败,则会发生锁膨胀,升级为重量级锁,并进入阻塞状态,等到之前持有锁的线程释放锁则会唤醒阻塞的进程重新竞争锁。


    -重量级锁

    重量级锁依赖于操作系统的互斥量(mutex) 实现。

3.java使用什么机制保证原子性操作呢?

    1.使用CAS+自旋锁方式

            但是存在三个问题

            - ABA问题:如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有变化,但是实际上却发生了变化。解决思路是,使用版本号,在变量前追加版本号,每次变量更新把版本号加1,如A-B-A  ==> 1A-2B-3A

            - 循环时间开销太大

                解决思路:jvm允许处理器使用pause指令

            -  只能保证一个共享变量的原子操作

                解决思路:JDK提供了AtomicReference类来保证引用对象之间的原子操作,就可以把多个变量放在一个对象里进行CAS操作了(还有一种将两个变量合并,如i=2,j=a ==> ij=2a)

    2.使用锁机制实现原子操作

            如偏向锁,轻量级锁,自旋锁,互斥锁

4.上下文切换

我们在获取锁和释放锁时都会造成上下文切换,如果频繁的上下文切换则会浪费资源

    如何减少上下文切换呢?

    - 使用CAS算法

    -使用最少线程

    -使用协程

5.并发工具类

1. CountDownLatch ---等待多线程完成


同步队列器

我们可以看到这里的CountDownLatch底层是实现了一个同步队列器的.


构造函数

这里有一个构造函数,当我们new CountDownLatch的时候实际上是在向同步队列器中设置了一个状态值,然后重写了tryAcquireShared()方法,这个地方就是获取同步状态的方法,可以看到这里是判断state==0?即只有state==0的时候才有机会获取到同步状态,否则其他情况下就都会加入到同步队列器中进行自旋等待。

额,如果想释放它,则必须让state==0所以重写了tryReleaseShared方法,这个方法用CAS保证了每次对state的减1操作,当state==0时就释放同步状态唤醒后继节点。


2.同步屏障CyclicBarrier

    让一组线程达到一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会被打开。

提供两种构造函数:

        1.)new CyclicBarrier(int count)//count 表示屏障拦截的线程数量,每个线程调用await()方法告诉CyclicBarrier直接达到屏障点。


代码演示

可以看到一直等到线程2到达同步点才释放同步状态。

2.)new CyclicBarrier(int count,Runnable Action);这个构造函数表示,当线程到达屏障时会优先处理第二个参数中的run方法。


代码演示

可以看到,CylicBarrier构造函数中的第二个参数被先执行了。一般可用于最后进行统计,比如最后汇总一年所消费的前,比如用一个线程计算一个月,最后用这个第二个参数进行统计所有的钱。

CylicBarrier底层的await()方法是用ReentrantLock 和该重入锁的Condition实现的,当我们调用初始化方法的时候,会初始一个parties和指向Runable 方法的参数。当我们调用await方法时,首先加锁操作,然后进行parties--操作,判断该index是否等于0,如果不等于0则调用Condition.await()被阻塞。一直等到其他线程调用await()方法将index减等于0时,则判断第二个参数是否存在,如果存在执行该方法,然后唤醒所有的等待线程,去竞争同步状态。依次执行后面的操作。



CylicBarrier 和 CountDownLatch的区别:

1.CylicBarrier 可以重复使用,而CountDownLach只能使用一次

2.CountDownLach底层使用的时同步队列器,而CylicBarrier 用的时Reentrantlock 和Condition

    3.控制线程数的Semaphore

一般用来做流量控制,特别时公有资源有限的应用场景,通过acquire来获取许可证,release()方法来释放许可证。

    4.线程间交换数据的Exchanger


6.AQS 队列同步器

1.)队列同步器的底层结构

同步队列中的节点(Node)用来保存获取同步状态失败的线程引用等待状态,以及前驱和后继节点,节点属性类型与名称。

        AQS 底层是维护着一个双链表的这样的形式来形成一个FIFO的队列,当一个线程获取到同步状态(锁)会被放入到同步器中,反之如果获取失败则会用CAS方式插入到尾部。

        同步器包含两个节点的类型引用,一个指向头节点,一个指向尾节点,当一个线程成功的获取到同步状态时,其他线程将无法获取到同步状态,转而被构造成为节点加入到同步队列,而这个加入队列的过程必须保证线程的安全,因此同步器提供一个基于CAS的设置尾部节点的方法,compareAndSetTail(Node expect,Node update)。

AQS结构

首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。节点的值就是对线程的引用

2.)独占式同步状态获取与释放

        - acquire(独占式获取)

独占式获取

        这是同步器提供的模板方法,该方法独占式获取同步状态,如果说获取到同步状态则由该方法返回,否则会进入到同步队列中等待。

            -tryAcquire 这个方法用于独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态,以下为简单实现。如果获取同步状态失败,则证明该线程没有持有锁。举个例子,比如1表示,准备获取锁,调用tryAcquire如果将state从0改成1了则证明该线程独占此锁,后面第二个进程进来,也想将state从0改成1则会返回false,证明该线程没有持有锁则进入了后后面的acquirQueued方法中了

                -acquirQueued(addWaiter(Node.EXCLUSIVE),arg)

    首先我们得先看 里面的addWaiter方法

                -addWaiter(Node mode)

首先新建一个Node标记为独占状态,通过CAS快速尝试将改节点插入到尾部,如果插入成功则返回该节点,否则调用enq方法

该方法采用自旋的方式,死循环替换尾节点,替换之前先判断当前尾节点是否为空,如果为空的话CAS将当前节点设置成头节点以及尾节点。最后返回的节点,去进入到acquirQueued方法中

                    - acquirQueued

这个方法中我们可以看到,如果该节点想重新获取同步状态的话,必须该节点的前驱节点是头节点,

否则一直自旋等待。或则还有一种方式,如果interrupted被标记为true也会去调用cancelAcquire方法

为什么这里 必须是前驱节点是头节点才获取同步状态呢,这里有两个原因:

1.头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需检查自己的前驱节点是否是头节点。

2.维护同步队列的FIFO原则。同时避免被过早通知(指的是前驱节点不是头节点的线程由于中断而被唤醒)。

节点自旋同步状态

-relase(同步器提供的独占式release)

   通过锁自定义的tryRelease(arg)来判断是否需要释放,比如前面的Mutex锁,当state=0时tryRelease()为true即释放,释放将头节点的后继节点唤醒。

总结:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋,移出队列(或停止自旋)的条件是前驱节点为头节点并且成功获取到了同步状态。在释放同步状态时,同步器调用tryRelase(int arg)方法释放同步状态,然后唤醒头节点的后继节点。

3.)共享式同步状态获取与释放

-acquireShared()

要想实现共享锁得话,首先自己得实现自己锁得tryAcquireShared方法

我们可以在以上方法看到,共享式同步器获取自旋过程中,也是首先判断前驱节点是否为头节点,且当前得TryAcquireShared 是否大于0,如果大于0前前驱节点为头节点,则表示该节点获得了同步状态。

- releaseShared

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