Jdk1.5 이후에는 java.util.concurrent.locks 패키지 아래에 스레드 동기화를 구현하는 일련의 인터페이스와 클래스가 있습니다. 스레드 동기화에 관해서라면 누구나 동기화 키워드를 떠올릴 것입니다.
이것은 java에 내장된 키워드로 스레드 동기화를 처리하는데 사용하는데, 이 키워드는 결함이 많고 사용하기가 매우 편리하지도 않고 직관적이지 않기 때문에 Lock이 나타나는데 아래에서는 Lock을 비교하여 설명하겠습니다.
일반적으로 동기화 키워드를 사용할 때 다음과 같은 문제가 발생합니다.
(1) 통제 불가능성, 잠금을 마음대로 잠그거나 해제할 수 없습니다.
(2) 효율성이 상대적으로 낮습니다. 예를 들어 현재 두 개의 파일을 동시에 읽고 있으면 읽기와 읽기가 서로 영향을 미치지 않습니다. 그러나 동기화를 달성하기 위해 읽기 개체에 사용되는 경우
스레드가 하나 있는 한 일단 스레드가 들어오면 다른 스레드는 기다려야 합니다.
(3) 스레드가 잠금을 획득했는지 여부를 알 수 있는 방법이 없습니다.
Lock은 위의 동기화 문제를 매우 잘 해결할 수 있으며, jdk1.5 이후에는 읽기-쓰기 잠금 등 다양한 잠금도 제공되지만 동기화
키를 사용할 때 주의할 점이 하나 있습니다. 수동으로 잠금을 해제하지만, 잠금을 사용하려면 수동으로 잠금을 해제해야 합니다. Lock 잠금장치에 대해 알아봅시다.
Lock은 상위 계층 인터페이스로, 프로토타입은 다음과 같으며 총 6가지 메서드를 제공합니다.
public interface Lock { // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁 void lock(); // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过 调用线程的Thread.interrupted()方法,来中断线程的等待过程 void lockInterruptibly() throws InterruptedException; // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待 boolean tryLock(); // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 实现线程通信,相当于wait和notify,后面会单独讲解 Condition newCondition(); }
이러한 메서드를 어떻게 사용하나요? 앞서 언급했듯이 Lock을 사용하려면 잠금을 수동으로 해제해야 합니다. 그러나 프로그램에서 예외가 발생하면 잠금을 해제할 수 없으므로 Lock을 사용할 때 다음과 같은 형식이 고정됩니다.
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally {// 必须使用try,最后在finally里面释放锁 l.unlock(); }
간단한 예를 살펴보겠습니다. 코드는 다음과 같습니다.
/** * 描述:Lock使用 */ public class LockDemo { // new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类 Lock lock = new ReentrantLock(); public void readFile(String fileMessage){ lock.lock();// 上锁 try{ System.out.println(Thread.currentThread().getName()+"得到了锁,正在读取文件……"); for(int i=0; i<fileMessage.length(); i++){ System.out.print(fileMessage.charAt(i)); } System.out.println(); System.out.println("文件读取完毕!"); }finally{ System.out.println(Thread.currentThread().getName()+"释放了锁!"); lock.unlock(); } } public void demo(final String fileMessage){ // 创建若干个线程 ExecutorService service = Executors.newCachedThreadPool(); // 提交20个任务 for(int i=0; i<20; i++){ service.execute(new Runnable() { @Override public void run() { readFile(fileMessage); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }); } // 释放线程池中的线程 service.shutdown(); } }
잠금과 동기화의 비교
1. 함수lock과 동기화는 둘 다 스레드 안전성을 해결하기 위해 사용됩니다. 문제를 위한 도구.
2. 소스sychronized는 Java의 키워드입니다. lock은 JUC 패키지에 제공되는 인터페이스입니다. 이 인터페이스에는 가장 일반적으로 사용되는 ReentrantLock(재진입 잠금)을 포함하여 많은 구현 클래스가 있습니다.
3. 잠금 강도sychronized는 두 가지 방법으로 잠금 강도를 제어할 수 있습니다. 메서드 수준에서 sychronized 키워드를 수정합니다.
코드 블록 장식.잠금 개체의 차이점:
잠금 개체가 정적 개체이거나 클래스 개체인 경우 이 잠금은 전역 잠금입니다.
잠금 개체는 일반 인스턴스 개체이며 이 잠금의 범위는 이 인스턴스의 수명 주기에 따라 다릅니다.
잠금의 강도는 lock()과 Unlock()의 두 가지 메서드에 의해 결정됩니다. 두 메서드 사이의 코드는 스레드로부터 안전함을 보장합니다. 잠금 범위는 잠금 인스턴스의 수명 주기에 따라 다릅니다.
4. 유연성
잠금은 동기화보다 더 유연합니다. Lock은 언제 잠그고 해제할지 독립적으로 결정할 수 있습니다. 잠금의 lock() 및 Unlock() 메서드를 호출하기만 하면 됩니다.
동기화 키워드이기 때문에 비차단 경쟁 잠금 방법을 구현할 수 없습니다. 한 스레드가 잠금을 획득한 후 다른 잠금은 잠금을 획득할 기회를 갖기 전에 해당 스레드가 해제될 때까지만 기다릴 수 있습니다.
5. 공정한 잠금 및 불공정한 잠금공정한 잠금: 여러 스레드가 잠금을 적용한 순서대로 잠금을 획득하며 대기열에 항상 첫 번째가 됩니다. 잠금을 얻기 위해 대기열에 넣습니다. 장점: 모든 스레드는 리소스를 얻을 수 있으며 굶어 죽지 않습니다.
단점: 낮은 처리량. 대기열의 첫 번째 스레드를 제외하고 다른 모든 스레드는 차단되며 CPU가 차단된 스레드를 깨우는 데 드는 비용이 높습니다.불공정한 잠금: 여러 스레드가 잠금을 획득하려고 하면 직접 잠금을 획득하려고 시도하고, 획득할 수 없으면 대기 대기열에 직접 들어가게 됩니다.
장점: 스레드를 깨우는 CPU 오버헤드를 줄일 수 있고 전체 처리량 효율성이 더 높으며 CPU가 모든 스레드를 깨울 필요가 없으므로 깨어난 스레드 수가 줄어듭니다.
단점: 대기열 중간에 있는 스레드는 잠금을 획득하지 못하거나 오랫동안 잠금을 획득하지 못해 결국 굶어 죽을 수도 있습니다.
잠금은 공정한 잠금과 불공정한 잠금(기본 불공정한 잠금)이라는 두 가지 메커니즘을 제공합니다.
동기화는 불공평한 잠금입니다.
동기화된 잠금 해제는 수동적이며 동기화된 동기화 코드 블록의 실행이 끝나거나 예외가 발생한 경우에만 해제됩니다. 잠금 잠금에서 예외가 발생하면 점유된 잠금은 적극적으로 해제되지 않으며 Unlock()을 사용하여 수동으로 해제해야 합니다. 따라서 일반적으로 동기화 코드 블록을 try-catch에 넣고 Unlock() 메서드를 작성합니다. 마지막으로 교착상태를 피하세요.
7. 잠금을 획득할 수 있는지 확인합니다.동기화가 불가능합니다. lock은 비차단 경쟁 잠금 방법인 trylock()을 제공하며 반환 값은 부울 유형입니다. 이는 잠금 획득을 시도하는 데 사용됨을 나타냅니다. 획득에 성공하면 true를 반환하고, 획득에 실패하면 false를 반환합니다.
8. 예약 방법synchronized는 개체 개체 자체의 wait,notify,notifyAll 메서드를 사용하는 반면 Lock은 Condition을 사용하여 스레드 간을 예약합니다.
9. 방해해도 될까요동기화는 잠금이 해제될 때까지만 기다릴 수 있으며 인터럽트에 응답할 수 없습니다.
잠금을 기다리는 동안 Interrupt()를 사용하여 중단할 수 있습니다.
10. 성능
경쟁이 치열하지 않으면 성능은 거의 같지만, 경쟁이 치열하면 잠금 성능이 더 좋아집니다.
Lock은 readwritelock을 사용하여 읽기와 쓰기를 분리하여 다중 스레드 읽기 작업의 효율성을 향상시킬 수도 있습니다.
11. 동기화된 잠금 업그레이드
동기화된 코드 블록은 한 쌍의 monitorenter/monitorexit 명령어로 구현됩니다. 모니터 구현은 운영 체제 내부의 뮤텍스 잠금에 전적으로 의존합니다. 사용자 모드에서 커널 모드로 전환해야 하기 때문에 동기화 작업은 미분화된 헤비급 작업입니다.
이제 JVM은 바이어스 잠금, 경량 잠금, 중량 잠금의 세 가지 잠금을 제공합니다.
편향 잠금:
경쟁이 발생하지 않으면 기본적으로 편향 잠금이 사용됩니다. 스레드는 CAS 작업을 사용하여 개체 헤더에 스레드 ID를 설정하여 개체가 현재 스레드 쪽으로 편향되었음을 나타냅니다.
목적: 많은 애플리케이션 시나리오에서 대부분의 개체의 수명 주기는 최대 하나의 스레드에 의해 잠깁니다. 편향된 잠금을 사용하면 경쟁이 없을 때 오버헤드를 줄일 수 있습니다.
경량 잠금:
JVM은 현재 스레드의 threadID와 Java 객체 헤더의 threadID를 비교하여 일치하지 않는지 확인합니다(예: 스레드 2가 잠금 객체를 놓고 경쟁하려고 함). Java 개체 헤더에 기록된 스레드 1이 살아 있는지 확인해야 합니다(편향된 잠금은 적극적으로 해제되지 않으므로 여전히 스레드 1의 저장된 threadID입니다). 잠금 개체는 여전히 편향된 상태입니다. 잠금(객체 헤더의 스레드 ID는 스레드 2의 스레드 ID임), 바이어스된 잠금이 취소되고 가벼운 잠금으로 업그레이드됩니다.
다른 스레드가 경량 잠금을 사용하여 리소스에 액세스하려는 경우 스핀 잠금 최적화를 사용하여 리소스에 액세스합니다.
목적: 잠금 개체를 두고 경쟁하는 스레드가 많지 않으며 스레드가 잠금을 오랫동안 유지하지 않습니다. 스레드를 차단하려면 CPU가 사용자 모드에서 커널 모드로 전환해야 하기 때문에 비용이 많이 듭니다. 차단 후 바로 잠금이 해제되면 이득이 손실보다 크기 때문에 이때는 스레드를 차단하지 않는 것이 좋습니다. 잠금이 해제될 때까지 기다리도록 회전시키십시오.
헤비웨이트 잠금:
스핀이 실패하면 자체 선택이 다시 실패할 확률이 높으므로 직접 헤비웨이트 잠금으로 업그레이드하여 스레드를 차단하고 CPU 소비를 줄입니다.
잠금이 중량급 잠금으로 업그레이드되면 잠금을 잡지 못한 스레드는 차단되고 차단 대기열에 들어갑니다.
위 내용은 Java 멀티스레딩에서 잠금을 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!