synchronized使用与字节码说明

synchronized的作用:被synchronized修饰的方法或者代码块,同一时刻最多只有一个线程执行这段代码。

public class SynchronizedDemo {
    public static final Object LOCK = new Object();

    public static void fn1(){
        synchronized (LOCK){
            System.out.println(Thread.currentThread().getName()+"运行");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * synchronized在静态方法上,锁是类对象SynchronizedDemo.class
     */
    public static synchronized void fn2(){
        System.out.println(Thread.currentThread().getName()+"运行");
    }

    /**
     * synchronized在实例方法上,锁是类实例this
     */
    public synchronized void fn3(){
        System.out.println(Thread.currentThread().getName()+"运行");
    }

    public static void main(String[] args) throws Exception{
        for (int i = 0; i < 3; i++) {
            new Thread(() -> fn1()).start();
        }
    }
}

synchronized同步代码块内抛出异常,JVM会释放当前线程的锁

public class SynchronizedDemo {
    public static final Object LOCK = new Object();

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread().getName() + "运行");
                int a = 0;
                if (a == 0) {
                    /**
                     * synchronized同步代码块内抛出异常,JVM会释放当前线程的锁
                     */
                    throw new RuntimeException("抛出异常");
                }

                try {
                    TimeUnit.SECONDS.sleep(3L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread().getName() + "运行");
            }
        });

        thread1.start();
        TimeUnit.MILLISECONDS.sleep(10);
        thread2.start();
    }
}

synchronized具备可重入性,同一个线程在外层函数获得锁之后,内层函数还可以再次直接获取该锁。

synchronized可重入性原理:每个对象拥有一个锁计数器,JVM负责跟踪对象被加锁的次数,线程第一次进入同步代码块,计数器变为1,相同线程在此对象上再次获得锁时,计数器递增。离开同步代码块,计数器递减,当计数为0时,锁被释放。

public class SynchronizedDemo {
    public static final Object LOCK = new Object();

    public static void fn1() {
        synchronized (LOCK) {
            System.out.println(Thread.currentThread().getName() + "运行fn1");
            fn2();
        }
    }

    public static void fn2() {
        synchronized (LOCK) {
            System.out.println(Thread.currentThread().getName() + "运行fn2");
        }
    }

    public static void main(String[] args) {
        new Thread(() -> fn1()).start();
    }
}

线程等待锁的时候处于BLOCKED阻塞状态,无法响应中断。一旦锁被其他线程获取了,当前线程只能阻塞,直到持有锁的线程释放锁。如果持锁线程不释放锁,BLOCKED线程只能永远等待下去。

public class SynchronizedDemo {
    public static final Object LOCK = new Object();

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread().getName() + " run");
                try {
                    TimeUnit.SECONDS.sleep(5L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread().getName() + " run");
            }
        });

        thread1.start();
        TimeUnit.MILLISECONDS.sleep(10L);
        thread2.start();
        TimeUnit.MILLISECONDS.sleep(10L);

        /**
         * 此时thread2等待其他线程释放锁,处于BLOCKED阻塞状态,无法响应中断
         */
        thread2.interrupt();

        thread1.join();
        thread2.join();
    }
}

在字节码层面理解synchronized

1、每个Java对象自带一个监视器锁(monitor),monitor的计数器为0,表示没有线程持有锁。
2、线程进入synchronized同步代码块,monitor的owner变量设置为该线程,表示锁被该线程占有,同时锁计数器加1。若重入,锁计数器递增。
3、在monitor被线程占有期间有其他线程尝试获取锁,这些线程会进入BLOCKED状态,并且这些线程会被封装为ObjectWaiter对象,放在一个等待队列中。
4、线程离开synchronized同步代码块,monitor锁计数器减1。当锁计数器减为0,monitor的owner变量设置为null,锁被线程释放,其他线程可以竞争锁。

public class SyncBlock {
    public void syncsTask(){
        synchronized (this){
            System.out.println("synchronized code");
        }
    }
}

编译代码:  javac SyncBlock.java

使用javap查看SyncBlock.class:  javap -verbose SyncBlock.class   。得到的部分代码如下:

        public void syncsTask();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter
             4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             7: ldc           #3                  // String synchronized code
             9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: aload_1
            13: monitorexit
            14: goto          22
            17: astore_2
            18: aload_1
            19: monitorexit
            20: aload_2
            21: athrow
            22: return

monitorenter是获取锁指令,monitorexit是释放锁指令。为了保证锁能释放,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,在异常处理器中执行monitorexit。这是为什么会有两条monitorexit的原因。

public class SyncMethod {
    public synchronized void syncTask(){
        System.out.println("synchronized code");
    }
}

synchronized用在方法上,使用javap得到的字节码如下

        public synchronized void syncTask();
          descriptor: ()V
          flags: ACC_PUBLIC, ACC_SYNCHRONIZED
          Code:
            stack=2, locals=1, args_size=1
               0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
               3: ldc           #3                  // String synchronized code
               5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
               8: return
            LineNumberTable:
              line 5: 0
              line 6: 8

字节码中并没有monitorenter,monitorexit,而是多了一个ACC_SYNCHRONIZED标记。方法被ACC_SYNCHRONIZED,线程必须先持有monitor才能进入方法,方法执行完毕,线程释放monitor。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值