synchronized 相关概念

1、启动方式

  

  .run()  和 .start()

  注意:直接执行线程的 run() 方法,但是线程调用 start() 方法时也会运行 run() 方法,区别就是一个是由线程调度运行 run() 方法,一个是直接调用了线程中的 run() 方法!!

2、线程中常用方法

  start:开始执行该线程

  stop:强制结束该线程执行

  sleep:线程进入等待

   join:Thread中 join() 方法的作用是调用线程等待该线程完成后,才能继续用下运行。

  例如:启动了 3 个线程分别为 a、b、c,

    a.join(b);  b.join(c)  则表示:

    b 线程需要等待 c 线程执行完毕之后再执行,而 a 线程需要等待 b 线程执行完毕之后再执行。

   wait:表示持有对象锁的线程 A 准备释放对象锁权限,释放 cpu 资源并进入等待状态。

  notify:表示持有对象锁的线程 A 准备释放对象锁权限,通知 jvm 唤醒某个竞争该对象锁的线程 X。 线程 A synchronized 代码执行结束并且释放了锁之后,线程 X 直接获得对象锁权限,其他竞争线程继续等待(即使线程 X 同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的 notify ,notifyAll 被调用)。

  notifyAll:notifyall 和 notify 的区别在于,notifyAll 会唤醒所有竞争同一个对象锁的所有线程,当已经获得锁的线程 A 释放锁之后,所有被唤醒的线程都有可能获得对象锁权限。

  这里有个问题需要引起注意:wait()与sleep()的区别,简单来说wait()会释放对象锁而sleep()不会释放对象锁

面试题:wait/notify/notifyall为什么需要在synchronized里面?

  1. wait 方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列, 而这些操作都和监视器是相关的,所以 wait 必须要获得一个监视器锁。

  2. 对于 notify 来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里?所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

  3. 每个对象可能有多个线程调用wait方法,所以需要有一个等待队列存储这些阻塞线程。这个等待队列应该与这个对象绑定,在调用wait和notify方法时也会存在线程安全问题所以需要一个锁来保证线程安全。

3、 synchronized(Object)

  其中 Object 不能使用 String、Integer 和 Long

  String 的情况:

    synchronized比较锁是使用的==,比较的是内存地址,因为每次都会new一个String,所以内存肯定不同,最后变成了没有上锁。使用String的intern方法。

    intern方法在newString的时候会先去字符串常量池中判断有没有,如果有就用,没有的话就new然后放进去,这样可以完美解决这个问题。

  Integer 和 Long:

    原因是Java的自动封箱和解箱操作在作怪。例如:i++ 实际上是 i = new Integer(i+1),所以执行完 i++ 后,i 已经不是原来的对象了,同步块自然就无效了

4、线程同步

  synchronized

  1. 锁的是对象不是代码

  2. this 锁定的是当前的方法, *.class 锁的是当前整个类即对该类所有实例起作用

  3. 锁定(同步)方法 和 非锁定(非同步)方法 可以在一起同时执行。

  4. 锁升级

    无锁 --> 偏向锁 --> 自旋锁(轻量级锁) --> 重量级锁

      

  无锁:MarkWord标志位01,没有线程执行同步方法/代码块时的状态。

  偏向锁:MarkWord标志位01(和无锁标志位一样),会在对象头和栈帧中的锁记录里存储锁偏向的线程ID。(我们知道,大多数情况下,锁总是由同一个线程多次获得,且操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高,故刚开始的时候,我们可以使用偏向锁来提升性能)当有其他线程尝试竞争偏向锁时,持有偏向锁的线程且不处于活动状态才会释放锁,进而升级为 轻量级锁也叫自旋锁

  自旋锁:MarkWord标志位00。顾名思义 就是当多个线程竞争同一把锁的时候,轻量级锁是采用自旋锁的方式来实现的,自旋锁分为固定次数自旋锁自适应自旋锁轻量级锁是针对竞争锁对象线程不多且线程持有锁时间不长的场景。

  自旋锁 --> 重量级锁过程:threadA 获取轻量级锁时会把对象头中的 MarkWord 复制一份到 threadA 的栈帧中创建用于存储锁记录的空间DisplacedMarkWord,然后使用CAS将对象头中的内容替换成 threadA 存储 DisplacedMarkWord 的地址。如果这时候出现 threadB 来获取锁,threadB 也跟 threadA 同样复制对象头的 MarkWord 到自己的 DisplacedMarkWord中,如果 threadA 锁还没释放,这时候那么 threadB 的 CAS 操作会失败,会继续自旋,当然不可能让 threadB 一直自旋下去,自旋到一定次数(固定次数/自适应)就会升级为重量级锁。

  重量级锁:通过对象内部监视器(monitor)实现,monitor本质是基于操作系统互斥(mutex)实现的,操作系统实现线程之间切换需要从用户态到内核态切换,成本非常高。

注:锁只可以升级不可以降级,但是偏向锁可以被重置为无锁状态。

Hotspot JVM中MarkWord存储格式如下:

32位存储格式:

  

64位存储格式:

  

 对线程 synchronized 与 lock 的区别详解请移步到下一篇博客:synchronized 与 lock 区别与场景

posted @ 2020-04-21 09:31  星火燎原智勇  阅读(243)  评论(0编辑  收藏  举报