>  기사  >  Java  >  Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

王林
王林앞으로
2023-05-10 18:55:141559검색

Lock 소개

Lock은 최상위 인터페이스이며 모든 메서드는 아래와 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

해당 하위 클래스 목록은 다음과 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

일반적으로 ReentrantLock을 사용하여 정의합니다. 이들 사이의 관계는 아래 그림에 나와 있습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

PS: Sync는 동기화 잠금을 의미하고 FairSync는 공정한 잠금을 의미하며 NonfairSync는 불공정 잠금을 의미합니다.

ReentrantLock 사용

모든 기술 학습은 사용에서부터 시작되므로 우리도 예외는 아닙니다. 먼저 ReentrantLock의 기본 사용을 살펴보겠습니다.

public class LockExample {
    // 创建锁对象
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        // 加锁操作
        lock.lock();
        try {
            // 业务代码......
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

ReentrantLock을 만든 후 두 가지 핵심 사항이 있습니다. 작업:

  • 잠금 작업: lock()

  • 잠금 해제 작업: Unlock()

ReentrantLock의 함정

1. ReentrantLock의 기본값은 불공평한 잠금입니다

많은 사람들이 생각할 것입니다(특히 초보 친구의 경우). ReentrantLock의 기본 구현은 공정한 잠금이지만 이는 사실이 아닙니다. ReentrantLock은 기본적으로 불공정한 잠금입니다(주로 성능 고려 사항 때문입니다).

예를 들어 다음 코드는

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 定义线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 加锁
                lock.lock();
                try {
                    // 打印执行线程的名字
                    System.out.println("线程:" + Thread.currentThread().getName());
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
        // 创建多个线程
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

The 위 프로그램의 실행 결과는 다음과 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

위의 실행 결과를 보면 ReentrantLock이 기본적으로 불공정 잠금인 것을 알 수 있습니다. 스레드의 이름은 생성된 순서대로 증가하므로 공정한 잠금이라면 스레드의 실행도 순서대로 증가해야 한다. 스레드 인쇄 순서가 잘못되었습니다. 설명 ReentrantLock은 기본적으로 불공정 잠금입니다.

ReentrantLock을 공정한 잠금으로 설정하는 것도 매우 간단합니다. ReentrantLock을 생성할 때 실제 구성 매개변수만 설정하면 됩니다. 다음 코드에서 볼 수 있듯이:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象(公平锁)
    private static final ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) {
        // 定义线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 加锁
                lock.lock();
                try {
                    // 打印执行线程的名字
                    System.out.println("线程:" + Thread.currentThread().getName());
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
        // 创建多个线程
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

위 프로그램의 실행 결과는 다음과 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

위 결과에서 볼 수 있듯이 ReentrantLock에 대한 실제 구성 매개변수를 명시적으로 설정하면 ReentrantLock이 공정한 잠금이 되고 스레드가 잠금을 획득하는 순서가 질서정연해집니다.

실제로 ReentrantLock의 소스 코드를 보면 공정한 잠금인지 불공정한 잠금인지 확인할 수 있습니다. ReentrantLock의 소스 코드 중 일부는 다음과 같이 구현됩니다.

 public ReentrantLock() {
     sync = new NonfairSync();
 }
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

위의 소스 코드에서 ReentrantLock은 기본적으로 불공평한 잠금을 생성합니다. 생성 시 구성 매개변수의 값이 명시적으로 true로 설정되면 공정한 잠금이 생성됩니다.

2. 마지막으로 잠금을 해제합니다.

ReentrantLock을 사용할 때 잠금을 해제해야 합니다. 그렇지 않으면 잠금이 항상 점유되고 잠금을 사용하는 다른 스레드가 영원히 기다리게 되므로 ReentrantLock을 사용합니다. 이때 반드시 마지막으로 잠금을 해제하여 잠금이 해제되는지 확인하시기 바랍니다.

카운터 예시

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();
        System.out.println("Hello,ReentrantLock.");
        // 此处会报异常,导致锁不能正常释放
        int number = 1 / 0;
        // 释放锁
        lock.unlock();
        System.out.println("锁释放成功!");
    }
}

위 프로그램의 실행 결과는 다음과 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

위 결과를 보면 Exception이 발생하면 정상적으로 Lock이 해제되지 않는 것을 알 수 있는데, 이는 잠금을 사용하는 다른 스레드가 영구적으로 대기 상태에 있게 됩니다.

긍정적인 예

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();
        try {
            System.out.println("Hello,ReentrantLock.");
            // 此处会报异常
            int number = 1 / 0;
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释放成功!");
        }
    }
}

위 프로그램의 실행 결과는 다음과 같습니다.

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

위 결과를 보면, 메소드에서 예외가 발생하더라도 프로그램에 영향을 미치지 않음을 알 수 있습니다. ReentrantLock 잠금 작업을 해제하여 이 잠금을 사용하는 다른 스레드가 정상적으로 획득하고 실행할 수 있도록 합니다.

3. 잠금은 여러 번 해제할 수 없습니다.

잠금 작업 횟수와 잠금 해제 작업 횟수는 일대일로 일치해야 하며, 잠금을 여러 번 해제할 수 없습니다. 이렇게 하면 프로그램에서 오류가 보고되기 때문입니다.

카운터 예시

하나의 잠금은 두 개의 잠금 해제 작업에 해당하며, 이로 인해 프로그램이 오류를 보고하고 실행을 종료하게 됩니다. 샘플 코드는 다음과 같습니다.

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 加锁操作
        lock.lock();

        // 第一次释放锁
        try {
            System.out.println("执行业务 1~");
            // 业务代码 1......
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }

        // 第二次释放锁
        try {
            System.out.println("执行业务 2~");
            // 业务代码 2......
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }
        // 最后的打印操作
        System.out.println("程序执行完成.");
    }
}

위 프로그램의 실행 결과는 다음과 같습니다. :

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

위의 결과를 보면, 두 번째 Unlock이 실행되면 프로그램에서 오류를 보고하고 실행을 종료하여 예외 이후의 코드가 정상적으로 실행되지 않는 것을 알 수 있습니다.

4.lock 不要放在 try 代码内

在使用 ReentrantLock 时,需要注意不要将加锁操作放在 try 代码中,这样会导致未加锁成功就执行了释放锁的操作,从而导致程序执行异常。

反例

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // 创建锁对象
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        try {
            // 此处异常
            int num = 1 / 0;
            // 加锁操作
            lock.lock();
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("锁释锁");
        }
        System.out.println("程序执行完成.");
    }
}

以上程序的执行结果如下: 

Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?

 从上述结果可以看出,如果将加锁操作放在 try 代码中,可能会导致两个问题:

  • 未加锁成功就执行了释放锁的操作,从而导致了新的异常;

  • 释放锁的异常会覆盖程序原有的异常,从而增加了排查问题的难度。

위 내용은 Java에서 ReentrantLock의 일반적인 함정은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제