Java多线程 可重入锁

Java多线程 可重入锁

Java 可重入锁

可重入锁的意思就是:同一个线程可以多次获得同一把锁。

隐式锁

synchronized关键字使用的锁(默认为可重入锁)

同步块

private static final Object KEY = new Object();

@Test
void reEnterLock() {
    new Thread(() -> {
        synchronized (KEY) {
            System.out.println(Thread.currentThread().getName() + "\t调用1");
            synchronized (KEY) {
                System.out.println(Thread.currentThread().getName() + "\t调用2");
                synchronized (KEY) {
                    System.out.println(Thread.currentThread().getName() + "\t调用3");
                }
            }
        }
    }, "可重入锁")
            .start();
}

控制台输出:

可重入锁	调用1
可重入锁	调用2
可重入锁	调用3

同步方法

@Test
void reEnterLockMethod() {
    m1();
}
public synchronized void m1() {
    System.out.println("方法1");
    m2();
}
public synchronized void m2() {
    System.out.println("方法2");
    m3();
}
public synchronized void m3() {
    System.out.println("方法3");
}

控制台输出:

方法1
方法2
方法3

字节码解析

Java代码

public void codeBlock() {
    synchronized (KEY) {
        System.out.println("synchronized code block");
    }
}

对应字节码

javap -c xxx.class

public void codeBlock();
  Code:
     0: getstatic     #15                 // Field KEY:Ljava/lang/Object;
     3: dup
     4: astore_1
     5: monitorenter
     6: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: ldc           #16                 // String synchronized code block
    11: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    14: aload_1
    15: monitorexit
    16: goto          24
    19: astore_2
    20: aload_1
    21: monitorexit
    22: aload_2
    23: athrow
    24: return

我们需要注意关键字monitorentermonitorexitmonitorenter表示加锁,monitorexit表示释放锁,那么为什么monitorexit出现了两次,也就是说释放了两次锁,因为Java代码要保证出异常后能够把锁给释放掉,所以在代码结束前再执行了一次锁的释放。

每个锁对象①拥有一个锁计数器和一个指向持有该锁的线程指针,当monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象线程设置为当前线程,并将其锁计数器加1。

①如同步块内的静态对象KEY

在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有锁的线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁被释放。

显式锁(Lock)

这里以ReentrantLock作为示例

private final static Lock LOCK = new ReentrantLock();
@Test
void reentrantLock() {
    new Thread(() -> {
        LOCK.lock();
        try {
            LOCK.lock();
            System.out.println(Thread.currentThread().getName() + "\t调用1");
            try {
                System.out.println(Thread.currentThread().getName() + "\t调用2");
            } finally {
                LOCK.unlock();
            }
        } finally {
            LOCK.unlock();
        }
    }, "Thread 1")
            .start();
    new Thread(() -> {
        LOCK.lock();
        try {
            LOCK.lock();
            System.out.println(Thread.currentThread().getName() + "\t调用1");
            try {
                System.out.println(Thread.currentThread().getName() + "\t调用2");
            } finally {
                LOCK.unlock();
            }
        } finally {
            LOCK.unlock();
        }
    }, "Thread 2")
            .start();
}

控制台输出:

Thread 1	调用1
Thread 1	调用2
Thread 2	调用1
Thread 2	调用2

使用显式锁一定要注意,Lock#lockLock#unlock一定要成对出现,否则会出现线程死锁的问题,正常情况下,加锁几次,解锁几次。

# Java  基础 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×