Synchronized升级过程是怎样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
要理解Synchronized,首先要清楚偏向锁,轻量级锁和重量级锁,在使用方面需要有wait/wait(time)/notify/notifyAll等,下面我们就来介绍一下Synchronized的流程和使用方法;
(Java SE 1.6中为了减少获得锁和释放锁带来的 性能消耗而引入的偏向锁和轻量级锁)
Synchronized的升级顺序是 无锁-->偏向锁-->轻量级锁-->重量级锁,顺内不可逆。
当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,偏向锁是一个可重入的锁,以后该线程在进入和退出该同步代码块时不需要花费 CAS 操作来加锁和解锁,而只需简单的测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁(当前线程的线程ID),如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下 Mark Word 中偏向锁的标识是否设置成 1(表示当前是偏向锁),如果偏向锁标识是1,则使用 CAS 进行锁获取,偏向锁标识不是1,则尝试使用 CAS 将对象头的偏向锁指向当前线程,上述两种CAS获取锁的操作,如果CAS操作成功则获取到了偏向锁,失败则代表出现了锁竞争,需要锁撤销操作。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销需要等待拥有偏向锁的线程到达全局安全点(在这个时间点上没有字节码正在执行),会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将锁的对象的对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行**(判断是否需要持有锁),遍历偏向对象的锁记录,查看使用情况,如果还需要持有偏向锁,则偏向锁升级为轻量级锁**,如果不需要持有偏向锁了,则将锁对象恢复成无锁状态,最后唤醒暂停的线程。
线程在执行同步块之前,JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中,官方称为 Displaced Mark Word。然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁,自旋有一定次数,如果超过设置自旋的次数则升级到重量级锁,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
轻量级解锁时,会使用原子的 CAS 操作来将 Displaced Mark Word 替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
**例如:**T1线程持有锁,T2线程自旋,但是T2线程自旋最大次数已经过了,则自旋失败,进行锁升级到重量级锁,T2线程阻塞,这时T1执行完了同步代码块,进行轻量级锁解锁,但是这时Mark Word中的标志位已经从原来的00(偏向锁)变成了10(中练级锁),解锁会CAS失败,T1会进行解锁(释放监视器,释放锁),并唤醒线程T2.
Synchronized是非公平锁,Synchronized在线程进入阻塞队列时,等待的线程会先尝试获取锁,如果获取不到就进入阻塞队列,这明显对于已经进入队列的线程是不公平的。
锁 | 优点 | 缺点 | 场景 |
---|---|---|---|
偏向锁 | 加解锁不需要过多的资源消耗,和非同步方法的相比仅仅是纳秒的差距 | 如果存在所竞争,会有额外的锁撤销操作 | 适用于只有一个线程访问的场景 |
轻量级锁 | 竞争线程不会阻塞,会自旋,减少了上线文切换。 | 如果始终得不到锁,会消耗cpu资源 | 追求响应时间,同步代码块多为计算,执行快的场景 |
重量级锁 | 没啥优点 | 线程阻塞响应时间慢 | 同步代码块执行时间较长的场景使用 |
1: Synchronized 是java的内置锁,也是排它锁和非公平锁,排它锁也就是当前线程获取锁后,其他线程都会阻塞挂起 ,非公平锁是在线程后去锁的时候会先尝试后去锁,获取不到在进行阻塞。
2: Synchronized 是如何保证 '原子性' 的?是因为进入 Synchronized 块的内存语义是把 Synchronized 块内使用的 '工作内存清除', 这样在使用共享变量时就会直接存主内存中获取并复制到工作你内存中,在退出 Synchronized 语句块时 会把 工作内存中计算过的 '共享变量' 更新到主内存中。
3: 获取到 Synchronized 锁 ,都是 '对象锁'而非'代码块锁' (锁的都是对象或类,而不是某个方法),因此 Synchronized 是具有可重入性,在获取到该对象锁后可以不用再次获取该对象其他方法的锁,直接进入。
4: 如果是 Synchronized 用在 static 上, 就代表是类锁(.class),无论创建多少个对象都不可行;
wait和sleep区别在于wait会释放锁, 但是sleep不会释放锁 ,sleep会导致线程阻塞挂起。
wait/wait(timeout)/notify/notifyAll 方法仅可以在获取到锁后才可以使用。
wait: 线程等待。
wait(time): 线程等待,如果时间超过了设置的time,则继续执行。
notify: 随机唤醒一个等待的线程。
notifyAll: 唤醒全部等待线程。
/** * @Auther: concurrenncy * @Date: 2019-03-25 16:43 * @Company: 随行付支付有限公司 * @maill: lan_tao@suixingpay.com * @Description: wait 和 sleep 区别在于 wait会释放锁, 但是 sleep 不会 ,sleep会导致线程阻塞挂起 */ public class WaitAndNotifyTest { private static Object obj = new Object(); public static void main(String[] args) { // 创建线程 thread1 Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " begin wait..."); synchronized (obj) { obj.wait(); } System.out.println(Thread.currentThread().getName() + " end wait..."); } catch (Exception e) { e.printStackTrace(); } } }, "thread1"); // 创建线程 thread2 Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " begin wait..."); synchronized (obj) { obj.wait(); } System.out.println(Thread.currentThread().getName() + " end wait..."); } catch (Exception e) { e.printStackTrace(); } } }, "thread2"); // 启动 thread1.start(); thread2.start(); try { // 睡眠一秒 Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } // 如果调用 notify 的线程未获取 对象锁,在调用 notify 的时候会抛出 java.lang.IllegalMonitorStateException 异常 synchronized (obj) { // 唤醒 使用 obj 调用 wait 方法的其中一个线程 (随机) obj.notify(); // 唤醒 使用呢 obj 调用 wait 方法的所有线程 obj.notifyAll(); } } }
关于Synchronized升级过程是怎样的问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。