synchronized 的使用以及底层简单分析

2 篇文章 0 订阅

synchronized 的使用

实例级别
public class UtilInstance {
    private int num;

    public  synchronized void pp() {//这是实例级别的
        num++;
        System.out.println("pp " + num);
    }
}

public class ContentInstance {
    public static void main(String[] args) {
        final CountDownLatch latch=new CountDownLatch(1);
        final UtilInstance aaa = new UtilInstance();
        for(int i=0;i<10;i++){
            new Thread(new Runnable() {
                public void run() {
                    try {
                        latch.await();
                       aaa.pp();//测试实例级别
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            new Thread(new Runnable() {
                public void run() {
                    try {
                        latch.await();
                        aaa.pp();
                        //测试实例级别
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            latch.countDown();
        }
    }
}

output

pp 1
pp 2
pp 3
pp 4
pp 5
pp 6
pp 7
pp 8
pp 9
pp 10
pp 11
pp 12
pp 13
pp 14
pp 15
pp 16
pp 17
pp 18
pp 19
pp 20

我们可以看到,synchronized放在普通方法上,他的锁是实例级别,两个线程调用同一个实例的这个方法,就能实现同步执行

后面我思考了一下,既然是实例锁,那么假如我的这个类里面有另外一个sync方法,然后我在调用第一个sync方法的时候,再调用第二个sync方法,会等待第一个sync方法执行完才执行第二个sync方法吗,然后我测试了一下,在原有代码上加了一个sync 方法

public class UtilInstance {
    private int num;

    ......

    public  synchronized void ss() {//这是实例级别的
        num++;
        System.out.println("ss -->" + num);
    }
}

			new Thread(new Runnable() {
                public void run() {
                    try {
                        latch.await();
                       aaa.ss();//测试实例级别
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            
            new Thread(new Runnable() {
                public void run() {
                    try {
                        latch.await();
                        aaa.pp();
                        //测试实例级别
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            latch.countDown();

output:

ss -->1
ss -->2
休眠五秒
pp 3
ss -->4
休眠五秒
pp 5
ss -->6
休眠五秒
pp 7

哈哈,真相大白了,同一个实例,多线程调用里面的sync方法,会等待上一个sync方法执行完后释放实例对象锁,然后下一个sync方法获得实例对象锁,然后调用

静态方法级别
public class UtilStaticMethod {
    private static int num;
    public static synchronized void sss(){
        num++;
        System.out.println(num);
    }
}

public class ContentStaticMethod {
    public static void main(String[] args) {
        final CountDownLatch countDownLatch=new CountDownLatch(1);
        for(int i=0;i<1000;i++){
            new Thread(new Runnable() {
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    UtilStaticMethod.sss();
                }
            }).start();
        }
        countDownLatch.countDown();
    }
}
546
547
548
549
550
551
552
553
554
555

我们可以看到,这个也做到了多线程之间调用方法的同步,这里也是对象锁,锁定的是当前的class

为了验证sync static 方法锁的是class,我们加一个方法来验证下

public static synchronized void syncMethod(){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num++;
        System.out.println(num+"  syncMethod,休眠5秒后");
    }

    public static void syncClass(){
        synchronized (UtilStaticMethod.class){
            num++;
            System.out.println(num+" syncClass");
        }
    }
    
    final CountDownLatch countDownLatch=new CountDownLatch(1);
        for(int i=0;i<1000;i++){
            new Thread(new Runnable() {
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    UtilStaticMethod.syncMethod();
                }
            }).start();
            new Thread(new Runnable() {
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    UtilStaticMethod.syncClass();
                }
            }).start();
        }
        countDownLatch.countDown();

output:

43  syncMethod,休眠5秒后
44  syncMethod,休眠5秒后
45 syncClass
46 syncClass
47  syncMethod,休眠5秒后
48 syncClass
49  syncMethod,休眠5秒后
50 syncClass
51  syncMethod,休眠5秒后
52 syncClass

我们可以看到,正如我们猜想的那样,sync static 方法,锁的是当前class,也是对象锁

代码块级锁
public void test(){
    private Object lock=new Object();
    synchronized(lock){
        dothing();
    }

}

这种也是对象锁

简单分析

接下来我们看看这个sync(对象)到底做了什么

javap -c UtilStaticMethod

 public static void syncClass();
    Code:
       0: ldc           #16                 // class sync/UtilStaticMethod
       2: dup
       3: astore_0
       4: monitorenter
       5: getstatic     #7                  // Field num:I
       8: iconst_1
       9: iadd
      10: putstatic     #7                  // Field num:I
      13: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #9                  // class java/lang/StringBuilder
      19: dup
      20: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
      23: getstatic     #7                  // Field num:I
      26: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      29: ldc           #17                 // String  syncClass
      31: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      34: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: aload_0
      41: monitorexit
      42: goto          50
      45: astore_1
      46: aload_0
      47: monitorexit
      48: aload_1
      49: athrow
      50: return
    Exception table:
       from    to  target type
           5    42    45   any
          45    48    45   any
}

这里的字节码对应这里的java代码

public static void syncClass(){
        synchronized (UtilStaticMethod.class){
            num++;
            System.out.println(num+" syncClass");
        }
    }

我们慢慢分析到底做了什么

首先,ldc 命令是把一个值从常量池推到操作栈顶, 后面跟了个序号 #16,我们来看一下

 #16 = Class              #49            // sync/UtilStaticMethod

我们在来看一下 #49

 #49 = Utf8               sync/UtilStaticMethod

这里#16是保存了一个类全名叫sync.UtilStaticMethod的类,这里就是我们的锁对象

因为操作栈是临时的,所以这里dup复制了一份并放到栈顶,然后astore_0讲操作栈顶的数据放到本地变量

然后就是monitorenter,使用那个锁对象(我猜测是使用刚才复制的那个存放到本地变量表里的对象,不过看了源码分析,看调的方法是从栈里面获取锁对象,然后调用锁方法的),后面执行这个方法的时候会判断是否有锁没解,再执行或者等待放到等待队列(不过这里的jvm源码我没找到)

然后就是获得定义的num字段,自增,然后StringBuilder 进行append数据,然后调用打印方法

最后就是monitorexit,释放那个锁对象

完整代码可以去我的GitHub

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`synchronized` 关键字的底层实现和锁的获取与释放过程涉及到了 Java 虚拟机(JVM)、Java 对象头和操作系统等多个层面。 当一个线程访问一个被 `synchronized` 修饰的方法或代码块时,JVM 会首先尝试获取这个同步锁。如果这个锁没有被其他线程占用,则该线程可以顺利获取锁,并进入临界区。如果这个锁已经被其他线程占用,则该线程会进入阻塞状态,直到获取到锁为止。 在 Java 对象头中,有一个字段用于存储这个对象的 Mark Word,这个字段中的一部分用于存储锁的信息,例如锁的状态、持有锁的线程 ID 等。当一个线程获取到锁时,会将这个锁的状态设置为“锁定”,同时将持有锁的线程 ID 记录在 Mark Word 中。当这个线程释放锁时,会将锁的状态设置为“未锁定”,并清除持有锁的线程 ID。 在操作系统层面,锁的获取和释放需要通过操作系统提供的原语来实现,例如 Linux 中的 futex。当线程在获取锁时,如果锁已经被其他线程占用,则它会进入阻塞状态。操作系统会将这个线程加入到等待队列中,并在锁被释放时通知到这个线程,使其重新竞争锁。当线程释放锁时,操作系统会将等待队列中的一个或多个线程唤醒,并将锁的所有权转移到其中一个线程手中。 总的来说,`synchronized` 关键字的实现涉及到了多个层面的细节,包括 Java 虚拟机、Java 对象头和操作系统等。了解其底层实现原理有助于我们更好地理解和使用锁机制,从而编写出更加高效和健壮的多线程程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值