Java 멀티스레드 기반 – Lock 클래스

앞서 말했듯이 JVM은 synchronized 키워드를 제공하여 변수에 대한 동기화 접근을 실현하고wait와 notify로 스레드 간 통신을 실현한다.jdk1.5 이후 JAVA는 Lock 클래스를 제공하여synchronized와 같은 기능을 실현하고 Condition을 제공하여 스레드 간의 통신을 표시한다.
Lock 클래스는 자바 클래스가 제공하는 기능으로 풍부한api는 Lock 클래스의 동기화 기능을synchronized의 동기화보다 더욱 강하게 한다.이 글의 모든 코드는 있습니다Lock 클래스 예제 코드
본고는 주로 내용을 소개한다.
  • Lock 클래스
  • Lock 클래스 기타 기능
  • 조건류
  • Condition 클래스 기타 기능
  • 읽기와 쓰기 자물쇠
  • Lock 클래스
    Lock 클래스는 실제로 하나의 인터페이스입니다. 우리가 실례화할 때 실제로 이 인터페이스의 클래스를 실례화했습니다. Lock lock = new Reentrant Lock ().synchronized를 사용할 때,synchronized는 방법을 수식하거나 코드 블록을 동기화할 수 있습니다.
    앞에서 말했듯이 동기화 처리가 필요한 코드 설정 대상 모니터는 전체 방법보다synchronized로 수식하는 것이 좋다.Lock 클래스의 사용법도 마찬가지입니다. Lock 대상 lock을 통해 lock을 사용합니다.lock으로 자물쇠를 채우고 lock으로.자물쇠를 풀어줍니다.둘 사이에 동기화 처리가 필요한 코드를 배치합니다.
    구체적인 예는 다음과 같다.
    
    public class MyConditionService {
     private Lock lock = new ReentrantLock();
     public void testMethod(){
      lock.lock();
      for (int i = 0 ;i < 5;i++){
       System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
      }
      lock.unlock();
     }
    }
    
    테스트 코드는 다음과 같습니다.
    
      MyConditionService service = new MyConditionService();
      new Thread(service::testMethod).start();
      new Thread(service::testMethod).start();
      new Thread(service::testMethod).start();
      new Thread(service::testMethod).start();
      new Thread(service::testMethod).start();
    
      Thread.sleep(1000 * 5);
    
    결과가 너무 길어서 내놓지 않으니 구체적으로 내 원본을 볼 수 있다.한 마디로 하면 모든 라인의 인쇄 1-5는 동기화되어 순서가 혼란스럽지 않다.
    아래의 예를 통해 알 수 있듯이 Lock 대상이 자물쇠를 채울 때도 하나의 대상 자물쇠이다. 대상 모니터의 라인을 지속해야만 동기화 코드를 실행할 수 있고 다른 라인은 이 라인이 대상 모니터를 방출하기를 기다릴 수 있다.
    
    public class MyConditionMoreService {
     private Lock lock = new ReentrantLock();
     public void methodA(){
      try{
       lock.lock();
       System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() +
         " time=" + System.currentTimeMillis());
       Thread.sleep(1000 * 5);
       System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() +
         " time=" + System.currentTimeMillis());
      }catch (Exception e){
       e.printStackTrace();
      }finally {
       lock.unlock();
      }
     }
     public void methodB(){
      try{
       lock.lock();
       System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() +
         " time=" + System.currentTimeMillis());
       Thread.sleep(1000 * 5);
       System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() +
         " time=" + System.currentTimeMillis());
      }catch (Exception e){
       e.printStackTrace();
      }finally {
       lock.unlock();
      }
     }
    }
    
    테스트 코드는 다음과 같습니다.
    
     public void testMethod() throws Exception {
      MyConditionMoreService service = new MyConditionMoreService();
      ThreadA a = new ThreadA(service);
      a.setName("A");
      a.start();
      ThreadA aa = new ThreadA(service);
      aa.setName("AA");
      aa.start();
      ThreadB b = new ThreadB(service);
      b.setName("B");
      b.start();
      ThreadB bb = new ThreadB(service);
      bb.setName("BB");
      bb.start();
      Thread.sleep(1000 * 30);
     } 
    public class ThreadA extends Thread{
     private MyConditionMoreService service;
     public ThreadA(MyConditionMoreService service){
      this.service = service;
     }
     @Override
     public void run() {
      service.methodA();
     }
    }
    public class ThreadB extends Thread{
     private MyConditionMoreService service;
     public ThreadB(MyConditionMoreService service){
      this.service = service;
     }
     @Override
     public void run() {
      super.run();
      service.methodB();
     }
    }
    
    결과는 다음과 같습니다.
    
    methodA begin ThreadName=A time=1485590913520
    methodA end ThreadName=A time=1485590918522
    methodA begin ThreadName=AA time=1485590918522
    methodA end ThreadName=AA time=1485590923525
    methodB begin ThreadName=B time=1485590923525
    methodB end ThreadName=B time=1485590928528
    methodB begin ThreadName=BB time=1485590928529
    methodB end ThreadName=BB time=1485590933533
    Lock 클래스 자물쇠가 개체 자물쇠임을 알 수 있습니다.같은 lock 대상에 대한 lock.lock은 대상 모니터의 라인을 얻어야 동기화 코드를 실행할 수 있습니다. 다른 라인은 모두 기다려야 합니다.
    이 예에서 자물쇠를 넣는 것과 자물쇠를 놓는 것은try-finally이다.이런 장점은 어떤 이상이 발생하는 상황에서도 자물쇠의 방출을 보장할 수 있다는 것이다.
    Lock 클래스 기타 기능
    만약에 Lock류가 lock과 unlock 방법만 있다면 너무 간단하다. Lock류는 풍부한 자물쇠를 넣는 방법과 자물쇠를 넣는 상황에 대한 판단을 제공한다.주로 있다
  • 자물쇠의 공평을 실현한다
  • 현재 루트를 가져오는 lock 호출 횟수, 즉 현재 루트 잠금 개수를 가져옵니다
  • 잠금 대기 라인을 가져옵니다
  • 지정한 스레드가 이 잠금을 기다리고 있는지 조회합니다
  • 이 잠금을 기다리는 라인이 있는지 확인하십시오
  • 현재 라인이 잠금을 가지고 있는지 조회합니다
  • 자물쇠가 라인에 소지되어 있는지 판단한다
  • 자물쇠를 넣을 때 끊기면 자물쇠를 넣지 않고 이상 처리에 들어간다
  • 자물쇠를 넣으려고 시도합니다. 만약 이 자물쇠가 다른 라인에 보유되지 않은 상황에서 성공합니다
  • 공평한 자물쇠를 실현하다
    실례화 자물쇠 대상을 만들 때 구조 방법은 두 가지가 있는데 하나는 무참 구조 방법이고 하나는 boolean 변수를 전달하는 구조 방법이다.전송 값이true일 때, 이 자물쇠는 공평한 자물쇠입니다.기본적으로 매개 변수를 전달하지 않는 것은 비공평한 자물쇠입니다.
    공평 자물쇠: 라인에 자물쇠를 넣는 순서에 따라 자물쇠를 얻는다
    비공평한 자물쇠: 무작위 경쟁으로 자물쇠 얻기
    이 밖에 JAVA는 자물쇠가 공평한 자물쇠인지 아닌지를 판단하기 위해 isFair()를 제공한다.
    현재 스레드 잠금 개수 가져오기
    Java는 현재 스레드의 잠금 개수를 얻기 위해 getHoldCount() 방법을 제공합니다.잠금 개수라는 것은 현재 루틴이 lock 방법을 호출하는 횟수입니다.일반적으로 하나의 방법은lock방법만 호출할 수 있지만, 동기화 코드에 다른 방법이 호출될 수도 있고, 그 방법 내부에 동기화 코드가 있을 수도 있다.이렇게 하면 getHoldCount() 반환 값이 1보다 큽니다.
    다음 방법은 자물쇠를 기다리는 상황을 판단하는 데 쓰인다
    잠금 대기 스레드 수 가져오기
    자바는 자물쇠가 풀리기를 기다리는 스레드의 개수를 얻기 위해 getQueueLength () 방법을 제공합니다.
    지정한 스레드가 이 잠금을 기다리고 있는지 조회합니다
    자바는hasQueuedThread(Thread thread)를 제공하여 이 Thread가 이 lock 대상의 방출을 기다리고 있는지 조회합니다.
    이 잠금을 기다리는 루틴이 있는지 조회합니다
    마찬가지로 자바는 자물쇠가 풀리기를 기다리는 라인이 있는지 간단하게 판단하는 HasQueuedThreads () 를 제공합니다.
    다음 방법은 자물쇠를 가지고 있는 상황을 판단하는 데 쓰인다
    현재 스레드가 잠겨 있는지 조회
    자바는 자물쇠가 풀리기를 기다리는 라인이 있는지 판단하는 방법을 제공할 뿐만 아니라, 현재 라인이 자물쇠를 가지고 있는지 여부를 판단하는 방법도 제공한다. 즉, isHeldByCurrentThread(), 현재 라인에 이 자물쇠가 있는지 판단한다.
    자물쇠가 라인에 소지되어 있는지 판단하다
    마찬가지로 자바는 자물쇠가 하나의 라인에 보유되었는지 간단하게 판단할 수 있다. 즉, isLocked()
    다음 방법은 여러 가지 방식으로 자물쇠를 채우는 데 쓰인다
    자물쇠를 넣을 때 끊기면 자물쇠를 넣지 않고 이상 처리에 들어간다
    Lock 클래스는 여러 가지 선택의 자물쇠 추가 방법을 제공합니다. lockInterruptibly () 도 자물쇠를 추가할 수 있지만 라인이 중단되면 자물쇠를 추가하는 데 실패하고 이상 처리 단계를 진행합니다.일반적으로 이런 상황은 이 라인에 인터럽트 표시가 되어 있다.
    추가 잠금 시도, 이 잠금이 다른 라인에 보유되지 않은 경우 성공
    Java는 tryLock () 방법을 제공하여 자물쇠를 잠그는 것을 시도합니다. 이 자물쇠가 다른 라인에 보유되지 않은 토대에서만 자물쇠를 잠그는 데 성공할 수 있습니다.
    위에서 Lock 클래스를 소개하여 코드의 동기화 처리를 실현하고, 다음에는condition 클래스를 소개하여wait/notify 메커니즘을 실현한다.
    조건 클래스
    Condition은 Java가 대기/알림을 실현하기 위한 클래스를 제공하고, Condition 클래스는wait/notify보다 풍부한 기능을 제공하며, Condition 대상은 lock 대상에서 만든 것입니다.그러나 같은 자물쇠는 여러 Condition 객체, 즉 여러 객체 모니터를 만들 수 있습니다.이런 장점은 깨우는 라인을 지정할 수 있다는 것이다.notify 깨우는 라인은 무작위로 깨우는 것입니다.
    다음은 간단한 기다림/알림을 보여주는 예입니다.
    
    public class ConditionWaitNotifyService {
     private Lock lock = new ReentrantLock();
     public Condition condition = lock.newCondition();
     public void await(){
      try{
       lock.lock();
       System.out.println("await  " + System.currentTimeMillis());
       condition.await();
       System.out.println("await " + System.currentTimeMillis());
      }catch (Exception e){
       e.printStackTrace();
      }finally {
       lock.unlock();
      }
     }
     public void signal(){
      try{
       lock.lock();
       System.out.println("sign " + System.currentTimeMillis());
       condition.signal();
      }finally {
       lock.unlock();
      }
     }
    }
    
    테스트 코드는 다음과 같습니다.
    
      ConditionWaitNotifyService service = new ConditionWaitNotifyService();
      new Thread(service::await).start();
      Thread.sleep(1000 * 3);
      service.signal();
      Thread.sleep(1000);
    결과는 다음과 같습니다.
    
    await  1485610107421
    sign 1485610110423
    await 1485610110423
    
    조건 대상은 자물쇠를 통과합니다.newCondition()을 사용하여 생성합니다.await () 는 라인을 기다리는 것을 실현합니다. 라인이 막히는 것입니다.조건을 사용하다.깨우기 라인을 실현하기 위해signal ()깨우는 라인은 같은conditon 대상에서await () 방법을 호출하여 막힙니다.또한wait/notify와 마찬가지로await()와signal()도 동기화 코드 영역에서 실행됩니다.
    또한await가 끝난 문장은 알림을 받은 후에야 실행됨을 알 수 있으며,wait/notify의 기능을 실현했습니다.다음 예는 깨우쳐 만든 라인을 보여주는 것이다.
    
      ConditionAllService service = new ConditionAllService();
      Thread a = new Thread(service::awaitA);
      a.setName("A");
      a.start();
      Thread b = new Thread(service::awaitB);
      b.setName("B");
      b.start();
      Thread.sleep(1000 * 3);
      service.signAAll();
      Thread.sleep(1000 * 4);
    
    결과는 다음과 같습니다.
    
    begin awaitA  1485611065974ThreadName=A
    begin awaitB  1485611065975ThreadName=B
    signAll 1485611068979ThreadName=main
    end awaitA 1485611068979ThreadName=A
    이 결과는 같은 condition 대상으로 알림을 기다리는 것을 보여 줍니다.
    대기/통지 메커니즘을 간소화하는 것은 하나의 조건을 기다리는 것이다. 조건이 충족되지 않을 때 대기에 들어가고 조건이 충족될 때 대기하는 라인을 실행하는 것이다.이런 기능을 실현하기 위해wait의 코드 부분과 알림이 필요한 코드 부분은 반드시 같은 대상 모니터에 넣어야 한다.실행해야 여러 개의 막힌 스레드 동기화 실행 코드를 실현할 수 있으며, 알림을 기다리는 스레드도 동기화됩니다.wait/notify에 대해 말하자면 대상 모니터와 대기 조건이 결합된 즉synchronized(대상)는 이 대상을 이용하여wait와notify를 호출합니다.그러나 Condition 클래스에 대해서는 대상 모니터와 조건이 분리되고 Lock 클래스는 대상 모니터를 실현하고,condition 대상은 조건을 책임지고await와signal을 호출한다.
    Condition 클래스의 추가 기능
    wait 클래스와 가장 긴 대기 시간을 제공합니다. await Until (Date deadline) 이 지정한 시간에 도착하면 라인이 자동으로 깨워집니다.그러나await나awaitUntil이든 라인이 끊길 때 막히는 라인은 중단 이상이 발생할 수 있습니다.Java는 라인이 끊겼을 때 막힌 라인도 중단 이상이 발생하지 않도록 await Uninterruptibly 방법을 제공합니다.
    읽기 및 쓰기 자물쇠
    Lock 클래스는 ReentrantLock의 자물쇠를 제공하는 것 외에 ReentrantReadWriteLock의 자물쇠도 제공합니다.읽기와 쓰기 자물쇠는 두 개의 자물쇠로 나뉘는데, 한 개의 자물쇠는 읽기 자물쇠이고, 한 개의 자물쇠는 쓰기 자물쇠이다.읽기 자물쇠와 읽기 자물쇠 사이는 공유되고 읽기 자물쇠와 쓰기 자물쇠 사이도 서로 배척된다.
    다음 읽기 공유 예제를 참조하십시오.
    
    public class ReadReadService {
     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     public void read(){
      try{
       try{
        lock.readLock().lock();
        System.out.println(" " + Thread.currentThread().getName() +
        " " + System.currentTimeMillis());
        Thread.sleep(1000 * 10);
       }finally {
        lock.readLock().unlock();
       }
      }catch (InterruptedException e){
       e.printStackTrace();
      }
     }
    }
    테스트 코드 및 결과는 다음과 같습니다.
    
      ReadReadService service = new ReadReadService();
      Thread a = new Thread(service::read);
      a.setName("A");
      Thread b = new Thread(service::read);
      b.setName("B");
      a.start();
      b.start();
    
    결과는 다음과 같습니다.
    
     A 1485614976979
     B 1485614976981
    
    두 라인은 거의 동시에 동기화 코드를 실행한다.
    다음 예는 서로 배척하는 예를 쓰는 것이다
    
    public class WriteWriteService {
     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     public void write(){
      try{
       try{
        lock.writeLock().lock();
        System.out.println(" " + Thread.currentThread().getName() +
          " " +System.currentTimeMillis());
        Thread.sleep(1000 * 10);
       }finally {
        lock.writeLock().unlock();
       }
      }catch (InterruptedException e){
       e.printStackTrace();
      }
     }
    }
    
    테스트 코드 및 결과는 다음과 같습니다.
    
      WriteWriteService service = new WriteWriteService();
      Thread a = new Thread(service::write);
      a.setName("A");
      Thread b = new Thread(service::write);
      b.setName("B");
      a.start();
      b.start();
      Thread.sleep(1000 * 30);
    
    결과는 다음과 같습니다.
    
     A 1485615316519
     B 1485615326524
    
    두 스레드 동기화 실행 코드
    상호 배척의 예 읽기/쓰기:
    
    public class WriteReadService {
     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     public void read(){
      try{
       try{
        lock.readLock().lock();
        System.out.println(" " + Thread.currentThread().getName()
          + " " + System.currentTimeMillis());
        Thread.sleep(1000 * 10);
       }finally {
        lock.readLock().unlock();
       }
      }catch (InterruptedException e){
       e.printStackTrace();
      }
     }
     public void write(){
      try{
       try{
        lock.writeLock().lock();
        System.out.println(" " + Thread.currentThread().getName()
          + " " + System.currentTimeMillis());
        Thread.sleep(1000 * 10);
       }finally {
        lock.writeLock().unlock();
       }
      }catch (InterruptedException e){
       e.printStackTrace();
      }
     }
    }
    
    테스트 코드는 다음과 같습니다.
    
      WriteReadService service = new WriteReadService();
      Thread a = new Thread(service::write);
      a.setName("A");
      a.start();
      Thread.sleep(1000);
      Thread b = new Thread(service::read);
      b.setName("B");
      b.start();
      Thread.sleep(1000 * 30);
    
    결과는 다음과 같습니다.
    
     A 1485615633790
     B 1485615643792
    
    두 스레드 읽기와 쓰기 사이에도 동기화 실행 코드가 있다.
    총결산
    본고는 새로운 동기화 코드의 방식인 Lock 클래스와 새로운 대기/알림 메커니즘의 실현 Condition 클래스를 소개한다.본고는 단지 그들의 개념과 사용 방식을 간단하게 소개했을 뿐이다.Condition 및 읽기와 쓰기 자물쇠에 대한 더 많은 내용은 향후 블로그에 실릴 것입니다.
    이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!

    좋은 웹페이지 즐겨찾기