【Java】 之 Synchronized



一、简介


同步代码块一般使用 Javasynchronized 关键字来实现

两种加锁方式:

  1. 在方法签名处加 synchronized 关键字
  2. 使用 synchronized(对象 或 类) 进行同步

synchronized 锁特性由 JVM 负责实现

JVM底层是通过 监视锁 来实现 synchronized 同步的

监视锁(monitor): 是每个对象与生俱来的一个隐藏字段

使用步骤:

  1. 使用synchronized时, JVM会根据synchronized的当前环境, 找到对应对象的monitor

Tips: class 也是一个对象

  1. 再根据 monitor 的状态进行加、解锁的判断

**注意: ** synchronized同步块执行完成或者遇到异常, 锁会自动释放

相反: Lock 必须调用unlock()方法释放锁



二、synchronized 提供三种锁


(1) 偏向锁

偏向锁: JVM 利用 CAS 在对象头上设置线程 ID, 表示这个对象偏向与当前线程

目的: 为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销

使用过程:

在锁对象的对象头中有一个 ThreadId 字段

  1. 当第一个线程访问锁时, 如果该锁没有被其他线程访问过, 即 ThreadId字段为空, 那么JVM让其持有偏向锁, 并将ThreadId字段的值设置为该线程的 ID
  2. 当下一次获取锁时, 会判断当前线程的 ID 是否与锁对象的 ThreadId 一致

如果一致, 那么该线程不会重复获取锁, 从而提高了程序的运行效率

锁升级:

  1. 如果出现锁的竞争情况, 那么 偏向锁 会被撤销并升级为 轻量级锁
  2. 如果资源的竞争非常激烈, 会升级为 重量级锁

(2) 轻量级锁

轻量级锁 能提升程序同步性能的依据是 “对于绝大部分的锁, 在整个同步周期内都是不存在竞争的”

这是一个经验数据.
如果没有竞争, 轻量级锁使用 CAS 操作避免了使用互斥量的开销

如果有两条以上的线程争用同一个锁, 那轻量级锁就不再有效, 要膨胀为重量级锁, 锁标志的状态值变为 “10”, Mark Word中存储的就是指向重量级锁(互斥量) 的指针, 后面等待锁的线程也要进入阻塞状态


(3) 重量级锁


三、使用


public class SynchronizedTest {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {

        final Object lock = new Object();

        Thread[] ths = new Thread[10000];
        for(int i=0;i<ths.length;i++) {
            Thread thread = new Thread(new Runnable() {

                public void run() {
                    for(int i=0;i<10000;i++) {
                        synchronized (lock) {
                            count ++;
                        }
                    }
                }
            });
            ths[i] = thread;
        }
        long begin = System.currentTimeMillis();

        for(int i = 0; i < ths.length; i++) {

            ths[i].start();
            ths[i].join();
        }
        System.out.println(count);// 100000000
        System.out.println(System.currentTimeMillis() - begin); //4073
    }
}


四、源码分析


synchronized 同步方法 与 同步代码块的字节码不同:

public class SynchronizedTest {

    public synchronized void test1(){

    }
    
    public void test2(){

        synchronized (this){

        }
    }
}

// test1();

  public synchronized void test1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/donaldy/lock/cas/SynchronizedTest;

synchronized注在方法上,在字节码flags: ACC_SYNCHRONIZED会标识

// test2();

  public void test2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
      Exception table:
         from    to  target type
             4     6     9   any
             9    12     9   any

如图:
在这里插入图片描述

有一个monitorenter,二个monitorexit

为什么会有两个monitorexit

因为synchronized中发生异常,会自动释放锁。
所以最后一个monitorexit是在异常情况下时候使用的。

总结下,原理图如下:
在这里插入图片描述



五、实际运用


  1. 同步方法


六、参考资料


  1. <<码出高效>>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值