JavaRush /Java Blog /Random-KO /Java의 동시성. 튜토리얼 - 스레드로부터 안전한 구조.
0xFF
레벨 9
Донецк

Java의 동시성. 튜토리얼 - 스레드로부터 안전한 구조.

Random-KO 그룹에 게시되었습니다
병렬 프로그램 실행의 주요 위험(예: 원자성 또는 가시성 )을 살펴본 후 위에서 언급한 함정을 방지하는 데 도움이 되는 몇 가지 클래스 디자인을 살펴보겠습니다. 이러한 구성 중 일부는 스레드로부터 안전한 개체를 생성하므로 스레드 간에 안전하게 공유할 수 있습니다. 예를 들어, 불변 객체와 상태 비저장 객체를 살펴보겠습니다. 다른 보기는 다른 스레드가 스레드 로컬 변수와 같은 데이터를 수정하는 것을 방지합니다. Github 에서 모든 소스코드를 보실 수 있습니다 . 1. 불변 객체 불변 객체에는 상태(객체의 상태를 나타내는 데이터가 있음)가 있지만 객체의 인스턴스가 생성되면 생성 시 생성자에서 설정되며 상태는 변경할 수 없습니다. 스레드는 대체될 수 있지만 개체에는 여전히 하나의 가능한 상태가 있습니다. 모든 필드는 읽기 전용이므로 스레드가 개체의 데이터를 변경할 수 없습니다. 이 때문에 불변 스트림은 본질적으로 스레드로부터 안전합니다. Product 클래스는 변경할 수 없는 클래스를 보여줍니다. 생성자의 모든 필드를 채우고 그 중 아무것도 변경되지 않습니다. public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } } 어떤 경우에는 필드를 최종으로 만드는 것만으로는 충분하지 않습니다. 예를 들어 MutableProduct 클래스 는 모든 필드가 최종이지만 변경 불가능하지 않습니다. 위의 클래스가 변경 불가능하지 않은 이유는 무엇입니까? 그 이유는 클래스에서 참조를 검색할 수 있도록 허용하기 때문입니다. ' 카테고리 ' 필드는 변경 가능한 참조이므로 일단 수신되면 클라이언트가 이를 수정할 수 있습니다. 이를 설명하기 위해 다음 프로그램을 고려하십시오. 그리고 콘솔 출력은 다음과 같습니다. 'categories' 필드는 변경 가능하고 객체에서 획득되었으므로 클라이언트가 이 목록을 수정했습니다. 불변이어야 하는 객체가 변경되어 새로운 상태가 되었습니다. 목록의 내용을 표현하려면 불변 목록 표현을 사용할 수 있습니다. 2. 상태 없는 객체 상태 없는 객체는 불변 객체와 유사하지만 이 경우에는 상태가 없으며 심지어 상태도 없습니다. 객체가 상태 비저장 객체인 경우 호출 간에 데이터를 유지할 필요가 없습니다. 상태가 존재하지 않으므로 어떤 스레드도 개체의 메서드를 호출하여 다른 스레드의 결과에 영향을 미칠 수 없습니다. 이러한 이유로 상태 비저장 개체는 본질적으로 스레드로부터 안전합니다. ProductHandler 클래스는 이러한 유형의 개체의 예입니다. 여기에는 Product 개체에 대한 여러 작업이 포함되어 있으며 호출 사이에 데이터를 저장하지 않습니다. 작업 결과는 이전 호출이나 저장된 데이터와 무관합니다. sumCart 메서드에서 ProductHandler는 for-each 루프에서 사용할 수 있도록 Product 목록을 배열로 변환하여 모든 요소를 ​​반복합니다. 반복자 목록은 스레드로부터 안전하지 않으며 반복 중에 변경 사항이 있는 경우 ConcurrentModificationException이 발생할 수 있습니다. 귀하의 필요에 따라 다른 전략을 선택할 수 있습니다 . 3. 스레드 지역 변수 스레드 지역 변수는 스레드 내에서 정의되는 변수입니다. 다른 스레드는 이를 볼 수 없으며 변경하지 않습니다. 첫 번째 유형은 지역 변수입니다. 아래 예에서는 변수 total이 스레드 스택에 저장됩니다. public final class MutableProduct { private final String id; private final String name; private final double price; private final List categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List getCategories() { return this.categories; } public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } } public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); } Product categories A B C Modified Product categories B C public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } 기본 변수 대신 참조를 정의하고 반환하면 경계를 벗어나게 된다는 점을 명심하세요. 반환된 링크가 생성된 위치를 알 수 없습니다. sumCart 메소드를 호출하는 코드는 이를 정적 필드에 저장하고 다른 스레드에서 액세스할 수 있도록 허용할 수 있습니다. 두 번째 유형은 ThreadLocal 클래스입니다 . 이 클래스는 각 스레드에 대해 독립적인 저장소를 제공합니다. ThreadLocal에 저장된 값은 동일한 스레드의 모든 코드에서 사용할 수 있습니다. ClientRequestId 클래스는 ThreadLocale 클래스 사용의 예를 보여줍니다. ProductHandlerThreadLocal 클래스는 ClientRequestId를 사용하여 동일한 스레드에서 동일한 생성된 ID를 반환합니다. 기본 메서드를 실행할 때 콘솔 출력에는 각 스레드에 대해 서로 다른 ID가 표시됩니다. 예: ThreadLocale을 사용하려는 경우 스레드를 병합할 때(서버 애플리케이션에서와 같이) 일부 사용 위험을 주의해야 합니다. 요청 사이에 메모리 누수나 정보 누수가 발생할 수 있습니다. 이에 대해서는 너무 자세히 설명하지 않겠습니다. 왜냐하면... " ThreadLocale을 사용하여 발에 총을 쏘는 방법 "이라는 기사는 이것이 어떻게 일어날 수 있는지 잘 보여줍니다. 4. 동기화 사용 개체에 스레드로부터 안전한 액세스를 제공하는 또 다른 방법은 동기화를 이용하는 것입니다. 참조에 대한 모든 액세스를 동기화하면 주어진 시간에 하나의 스레드 객체만 참조에 액세스하게 됩니다. 이에 대해서는 향후 게시물에서 논의하겠습니다. 5. 결론 우리는 여러 스레드에서 액세스할 수 있는 간단한 개체를 만들 수 있는 몇 가지 방법을 살펴보았습니다. 객체가 여러 상태를 가질 수 있는 경우 다중 스레드 오류를 방지하는 것이 훨씬 더 어렵습니다. 반면에 객체가 하나의 상태만 가질 수 있거나 상태가 없을 수 있다면 동시에 객체에 액세스하는 여러 스레드에 대해 걱정할 필요가 없습니다. 원본은 여기에 있습니다 . public class ClientRequestId { private static final ThreadLocal id = new ThreadLocal () { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } } public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } } T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION