关键字: synchronized详解

Synchronized的使用
在应用Synchronized关键字时需要把握如下注意点:

  • 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
  • 每个实例都对应有一个自己的一把锁(this),不同实例之间互不影响;例如:锁对象是*.class以及Synchronized修饰的是static方法时,所有对象共用同一把锁;
  • Synchronized修饰的方法,无论方法正常执行完毕还是异常,都会释放锁;

对象锁
包括方法锁(默认的锁对象为this),同步代码块锁(自己指定锁对象)

代码块形式

  • 示例1:
public class SyncObjectLock implements Runnable{

    private static SyncObjectLock syncObjectLock = new SyncObjectLock();

    @Override
    public void run() {
        synchronized (syncObjectLock) {
            System.out.println("我是线程"+ Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程"+ Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(syncObjectLock);
        Thread thread2 = new Thread(syncObjectLock);
        thread1.start();
        thread2.start();
    }
}

输出结果:

我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束
  • 示例2:
public class SyncObjectLock2 implements Runnable{

    private static SyncObjectLock2 instance = new SyncObjectLock2();

    // 创建2把锁
    Object block1 = new Object();
    Object block2 = new Object();

    @Override
    public void run() {
        synchronized (block1){
            System.out.println("我是block1,线程"+ Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是block1,线程"+ Thread.currentThread().getName() + "结束");
        }
        synchronized (block2){
            System.out.println("我是block2,线程"+ Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是block2,线程"+ Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
    }
}

输出结果:

我是block1,线程Thread-0
我是block1,线程Thread-0结束
我是block2,线程Thread-0
我是block1,线程Thread-1
我是block2,线程Thread-0结束
我是block1,线程Thread-1结束
我是block2,线程Thread-1
我是block2,线程Thread-1结束

方法锁形式:synchronized修饰普通方法,锁对象默认为this

public class SyncObjectLock3 implements Runnable{

    private static SyncObjectLock3 instance = new SyncObjectLock3();

    @Override
    public void run() {
        method();
    }

    private synchronized void method(){
        System.out.println("我是线程"+ Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程"+ Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
    }
}

输出结果:

我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束

synchronized指定锁对象为class对象

public class SyncClassLock implements  Runnable{

    private static SyncClassLock instance = new SyncClassLock();

    @Override
    public void run() {
        synchronized (SyncClassLock.class) {
            System.out.println("我是线程"+ Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程"+ Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
    }
}

输出结果:

我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束





Synchronized原理分析

加锁和释放锁的原理
现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor命令)
深入JVM看字节码,生成如下代码:

public class SyncDemo1 {

    private Object obj;
    public SyncDemo1(Object obj) {
        this.obj = obj;
    }

    public void run(){
        synchronized (obj){
        }
        method();
    }
    public static  void method(){

    }
}

使用javac命令生成class文件:

javac SyncDemo1.java

使用javap命令反编译查看class信息:

javap -verbose SyncDemo1.class

得到如下信息:

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #2                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit
        15: aload_2
        16: athrow
        17: invokestatic  #3                  // Method method:()V
        20: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:

关注monitorentermonitorexit指令即可。
MonitorenterMonitorexit指令,会让对象在执行,使其锁计数器加1或减1。每一个对象在同一时间只与一个monitor(锁)相关联,而monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的monitor锁所有权的时候,monitorenter指令会发生如下三种情况之一:

  • monitor计数器为0,意味着目前没有被线程获得,那么被线程获得之后然后把计数器+1,一旦加1,别的线程想获得就只能等待;
  • 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那么计数器就会累加变成2,并且随着重入次数,会一直累加;
  • 这把锁已经被别的线程获取了,等待锁释放;
    monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是将monitor的计数器减1,如果减完之后计数器不是0,则代表刚才时重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表线程不再拥有该monitor的所有权,即释放锁。

可重入原理:加锁次数计数器

  • 什么是可重入?可重入锁?

可重入:(维基百科)若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段程序,这段程序又屌用了该子程序不会出错”,则称其为可重入(reentrant获取re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期结果,与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然时安全的。

可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。

例子:

public class SynchronizedDemo {

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        demo.method();
    }

    private synchronized void method(){
        System.out.println(Thread.currentThread().getId()+": method()");
        method1();
    }

    private synchronized void method1(){
        System.out.println(Thread.currentThread().getId()+": method1()");
        method2();
    }

    private synchronized void method2(){
        System.out.println(Thread.currentThread().getId()+": method2()");
        method3();
    }

    private synchronized void method3(){
        System.out.println(Thread.currentThread().getId()+": method3()");
    }
}

结合前文中加锁和释放锁的原理,不难理解:

  • 执行monitorenter获取锁
    monitor计数器=0,可获取锁
    执行method()方法,monitor计数器+1 ->1 (获取到锁)
    执行method1()方法,monitor计数器+1 ->2
    执行method2()方法,monitor计数器+1 ->3
    执行method3()方法,monitor计数器+1 ->4

  • 执行monitorexit指令
    method3()方法执行完,monitor计数器-1 ->3
    method2()方法执行完,monitor计数器-1 ->2
    method1()方法执行完,monitor计数器-1 ->1
    method()方法执行完,monitor计数器-1 -> 0 (释放锁)

这就是Synchronized的重入性,即在同一锁程中,每个对象拥有一个monitor计数器,当线程获取该对象锁之后,monitor计数器就会加1,释放锁后将monitor计数器减一,线程不需要再次获取同一把锁。

posted @   nihao1313  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示