Java 병렬 프로그래밍 예시(9): 로컬 스레드 변수 사용

공유 데이터는 병발 프로그램의 가장 관건적인 특성 중의 하나다.Thread 클래스를 계승하는 대상이든 Runnable 인터페이스를 실현하는 대상이든 이것은 매우 주도면밀한 부분이다.
Runnable 인터페이스를 실현하는 클래스의 대상을 만들고 이 대상을 사용하여 일련의 라인을 시작하면 모든 라인은 같은 속성을 공유합니다.다시 말하면, 만약 한 노드가 하나의 속성을 수정한다면, 나머지 모든 노드는 이 변화의 영향을 받을 것이다.
때때로, 우리는 같은 대상이 시작하는 다른 라인과 공유하지 않고, 라인 내에서 단독으로 사용할 수 있기를 더욱 희망한다.Java 병렬 인터페이스는 이 수요를 충족시키기 위해 뚜렷한 메커니즘을 제공합니다. 이 메커니즘을 로컬 스레드 변수라고 합니다.이 메커니즘의 성능도 매우 볼 만하다.
그 사실을 알다
아래와 같은 절차에 따라 예시 절차를 완성하다.
1. 우선, 상술한 문제가 있는 절차를 실현한다.UnsafeTask라는 클래스를 만들고 Runnable 인터페이스를 실현합니다.클래스에서 자바를 성명합니다.util.Date 유형의 개인 속성입니다.코드는 다음과 같습니다.

public class UnsafeTask implements Runnable {
    private Date startDate;
2. UnsafeTask를 실현하는run() 방법입니다. 이 방법은 startDate 속성을 실례화하고 그 값을 컨트롤러에 출력합니다.잠시 동안 휴면한 후 startDate 속성의 값을 다시 콘솔에 출력합니다.코드는 다음과 같습니다.

@Override
public void run() {
    startDate = new Date();
    System.out.printf("Starting Thread: %s : %s
",
            Thread.currentThread().getId(), startDate);

    try {
        TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.printf("Thread Finished: %s : %s
",
            Thread.currentThread().getId(), startDate);
}

3. 문제 프로그램의 주류를 실현한다.main() 메서드가 있는 클래스를 만듭니다. UnsafeMain.main() 방법에서 UnsafeTask 대상을 만들고 이 대상을 사용하여 10개의 Thread 대상을 만들고 10개의 라인을 시작합니다.각 라인 중간에서 2초 동안 휴면합니다.코드는 다음과 같습니다.

public class UnsafeMain {
    public static void main(String[] args) {
        UnsafeTask task = new UnsafeTask();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
4. 위의 논리를 보면 각 노선은 서로 다른 시작 시간을 가진다.그러나 아래의 출력 로그를 보면 같은 시간값이 많이 나온다.다음과 같습니다.

Starting Thread: 9 : Sun Sep 29 23:31:08 CST 2013
Starting Thread: 10 : Sun Sep 29 23:31:10 CST 2013
Starting Thread: 11 : Sun Sep 29 23:31:12 CST 2013
Starting Thread: 12 : Sun Sep 29 23:31:14 CST 2013
Thread Finished: 9 : Sun Sep 29 23:31:14 CST 2013
Starting Thread: 13 : Sun Sep 29 23:31:16 CST 2013
Thread Finished: 10 : Sun Sep 29 23:31:16 CST 2013
Starting Thread: 14 : Sun Sep 29 23:31:18 CST 2013
Thread Finished: 11 : Sun Sep 29 23:31:18 CST 2013
Starting Thread: 15 : Sun Sep 29 23:31:20 CST 2013
Thread Finished: 12 : Sun Sep 29 23:31:20 CST 2013
Starting Thread: 16 : Sun Sep 29 23:31:22 CST 2013
Starting Thread: 17 : Sun Sep 29 23:31:24 CST 2013
Thread Finished: 17 : Sun Sep 29 23:31:24 CST 2013
Thread Finished: 15 : Sun Sep 29 23:31:24 CST 2013
Thread Finished: 13 : Sun Sep 29 23:31:24 CST 2013
Starting Thread: 18 : Sun Sep 29 23:31:26 CST 2013
Thread Finished: 14 : Sun Sep 29 23:31:26 CST 2013
Thread Finished: 18 : Sun Sep 29 23:31:26 CST 2013
Thread Finished: 16 : Sun Sep 29 23:31:26 CST 2013
5. 앞에서 보듯이 우리는 로컬 스레드 변수 (thread-localvariables) 메커니즘을 사용하여 이 문제를 해결하려고 합니다.
6. SafeTask라는 클래스를 만들고 Runnable 인터페이스를 실현합니다.코드는 다음과 같습니다.

public class SafeTask implements Runnable {
7.ThreadLocal 형식의 대상을 설명합니다. 이 대상이 실례화될 때 initialValue () 방법을 다시 썼습니다. 이 방법에서 실제 날짜 값을 되돌려줍니다.코드는 다음과 같습니다.

private static ThreadLocal<Date> startDate = new
        ThreadLocal<Date>() {
            @Override
            protected Date initialValue() {
                return new Date();
            }
        };
8. SafeTask 클래스를 구현하는 run() 메서드.이 방법은 UnsafeTask의run() 방법과 마찬가지로 startDate 속성의 방법만 약간 조정합니다.코드는 다음과 같습니다.

@Override
public void run() {
    System.out.printf("Starting Thread: %s : %s
",
            Thread.currentThread().getId(), startDate.get());

    try {
        TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.printf("Thread Finished: %s : %s
",
            Thread.currentThread().getId(), startDate.get());
}

9. 이 보안 예시의 마스터 클래스와 비보안 프로그램의 마스터 클래스는 기본적으로 같지만 UnsafeTask를 SafeTask로 수정하면 됩니다.구체적인 코드는 다음과 같습니다.

public class SafeMain {
    public static void main(String[] args) {
        SafeTask task = new SafeTask();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
10. 프로그램을 실행하여 두 번의 입력의 차이점을 분석한다.
규범류의 명칭을 위해 본고에서 주류의 명칭은 원문과 약간 다르다.또 원문 절차와 문자 서술이 일치하지 않는다.잘못 쓴 것 같습니다.
그 까닭을 알다
다음은 안전 예시의 집행 결과입니다.결과에서 쉽게 알 수 있듯이 모든 스레드는 각자의 스레드에 속하는 startDate 속성 값을 가지고 있다.프로그램 입력은 다음과 같습니다.

Starting Thread: 9 : Sun Sep 29 23:52:17 CST 2013
Starting Thread: 10 : Sun Sep 29 23:52:19 CST 2013
Starting Thread: 11 : Sun Sep 29 23:52:21 CST 2013
Thread Finished: 10 : Sun Sep 29 23:52:19 CST 2013
Starting Thread: 12 : Sun Sep 29 23:52:23 CST 2013
Thread Finished: 11 : Sun Sep 29 23:52:21 CST 2013
Starting Thread: 13 : Sun Sep 29 23:52:25 CST 2013
Thread Finished: 9 : Sun Sep 29 23:52:17 CST 2013
Starting Thread: 14 : Sun Sep 29 23:52:27 CST 2013
Starting Thread: 15 : Sun Sep 29 23:52:29 CST 2013
Thread Finished: 13 : Sun Sep 29 23:52:25 CST 2013
Starting Thread: 16 : Sun Sep 29 23:52:31 CST 2013
Thread Finished: 14 : Sun Sep 29 23:52:27 CST 2013
Starting Thread: 17 : Sun Sep 29 23:52:33 CST 2013
Thread Finished: 12 : Sun Sep 29 23:52:23 CST 2013
Thread Finished: 16 : Sun Sep 29 23:52:31 CST 2013
Thread Finished: 15 : Sun Sep 29 23:52:29 CST 2013
Starting Thread: 18 : Sun Sep 29 23:52:35 CST 2013
Thread Finished: 17 : Sun Sep 29 23:52:33 CST 2013
Thread Finished: 18 : Sun Sep 29 23:52:35 CST 2013
스레드 로컬 변수는 모든 스레드에 속성의 복사본을 저장합니다.ThreadLocal의 get() 방법으로 변수의 값을 가져오고, set() 방법으로 변수의 값을 설정할 수 있습니다.만약 루트 로컬 변수에 처음 접근하고 이 변수가 값을 부여하지 않았다면, initialValue () 방법을 호출하여 모든 루트에 값을 초기화합니다.
영원히 끝이 없다
ThreadLocal 클래스는 이 방법을 호출하는 라인에 저장된 로컬 변수 값을 삭제하기 위해remove () 방법을 제공합니다.
또한 Java 병렬 API는 상속 가능한 모든 스레드 국부 변수의 초기 값을 수신하여 부모 스레드가 가지고 있는 값을 얻을 수 있도록 Inheritable ThreadLocal 클래스를 제공합니다.만약 스레드 A에 스레드 로컬 변수가 있다면, 스레드 A가 스레드 B를 만들 때, 스레드 B는 스레드 A와 같은 스레드 로컬 변수를 가지고 있을 것이다.하위 스레드의 로컬 변수를 초기화하기 위해childValue () 를 다시 쓸 수도 있습니다.이 방법은 부모 스레드에서 매개 변수 형식으로 전달된 스레드 로컬 변수의 값을 받아들일 것입니다.
나래주의
본고는 《Java7 Concurrency Cookbook》(D 오이고에서 《Java7 병발 예시집》으로 번역)에서 번역한 것으로 학습 자료로만 사용된다.권한이 없으면 어떠한 상업 행위에도 사용할 수 없다.
소소하다
다음은 이 절의 예시에 포함된 모든 코드의 전체 버전입니다.
UnsafeTask 클래스의 전체 코드:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 *
 * Date: 2013-09-23
 * Time: 23:58
 */
public class UnsafeTask implements Runnable {
    private Date startDate;

    @Override
    public void run() {
        startDate = new Date();
        System.out.printf("Starting Thread: %s : %s
",
                Thread.currentThread().getId(), startDate);

        try {
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("Thread Finished: %s : %s
",
                Thread.currentThread().getId(), startDate);
    }
}

UnsafeMain 클래스의 전체 코드:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.concurrent.TimeUnit;

/**
 *
 * Date: 2013-09-24
 * Time: 00:04
 */
public class UnsafeMain {
    public static void main(String[] args) {
        UnsafeTask task = new UnsafeTask();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

SafeTask 클래스의 전체 코드:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 *
 * Date: 2013-09-29
 * Time: 23:34
 */
public class SafeTask implements Runnable {
    private static ThreadLocal<Date> startDate = new
            ThreadLocal<Date>() {
                @Override
                protected Date initialValue() {
                    return new Date();
                }
            };

    @Override
    public void run() {
        System.out.printf("Starting Thread: %s : %s
",
                Thread.currentThread().getId(), startDate.get());

        try {
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("Thread Finished: %s : %s
",
                Thread.currentThread().getId(), startDate.get());
    }
}

SafeMain 클래스의 전체 코드:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.concurrent.TimeUnit;

/**
 *
 * Date: 2013-09-24
 * Time: 00:04
 */
public class SafeMain {
    public static void main(String[] args) {
        SafeTask task = new SafeTask();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

좋은 웹페이지 즐겨찾기