JavaRush /Java Blog /Random EN /Concurrency in Java. Tutorial - Thread-safe constructs.
0xFF
Level 9
Донецк

Concurrency in Java. Tutorial - Thread-safe constructs.

Published in the Random EN group
After covering the main risks of running parallel programs (such as atomicity or visibility ), we'll look at some class designs that will help us prevent the above-mentioned pitfalls. Some of these constructs create thread-safe objects, allowing us to safely share them between threads. As an example, we will look at immutable and stateless objects. Other views will prevent different threads from modifying data, such as thread local variables. You can view all source codes on Github . 1. Immutable Objects Immutable objects have state (have data that represents the state of the object), but it is set at creation time in the constructor, once an instance of the object has been created and the state cannot be changed. Although threads can alternate, an object still has one possible state. Since all fields are read-only, no thread can change the object's data. Because of this, an immutable stream is inherently thread-safe. The Product class demonstrates an immutable class. It fills out all of its fields in the constructor and none of them change: 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; } } In some cases, making the fields final will not be enough. For example, the MutableProduct class is not immutable, although all fields are final: Why is the class above not immutable? The reason is that we allow a reference to be retrieved from a class. The ' categories ' field is a mutable reference, so once received, the client can modify it. To illustrate this, consider the following program: And the console output: Since the 'categories' field is mutable and was obtained from an object, the client has modified this list. An object that should be immutable has been changed, resulting in a new state. If you want to represent the contents of a list, you can use an immutable list representation: 2. Stateless Objects Stateless objects are similar to immutable objects, but in this case they do not have a state, not even one. When an object is a stateless object, it does not have to persist any data between calls. Since no state exists, no thread can affect the result of another thread by calling the object's methods. For this reason, stateless objects are inherently thread-safe. The ProductHandler class is an example of this type of object. It contains multiple operations on Product objects, and it does not store any data between calls. The result of the operation is independent of previous calls or any stored data: In the sumCart method, ProductHandler converts the Product list to an array for use in a for-each loop to iterate through all the elements. The iterator list is not thread safe and may throw a ConcurrentModificationException if there are changes during iteration. Depending on your needs, you may choose a different strategy . 3. Thread Local Variables Thread local variables are those variables that are defined within a thread. No other threads see them and will not change them. The first type is local variables. In the example below, the variable total is stored on the thread's stack: 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; } Just keep in mind that if you define a reference instead of a primitive variable and return it, it will leave its bounds. You may not know where the returned link was created. The code that calls the sumCart method could store it in a static field and allow it to be accessed by different threads. The second type is the ThreadLocal class . This class provides independent storage for each thread. Values ​​stored in ThreadLocal are available to any code on the same thread. The ClientRequestId class shows an example of the use of the ThreadLocale class: The ProductHandlerThreadLocal class uses the ClientRequestId to return the same generated ID on the same thread: When executing the main method, the console output will show different IDs for each thread. For example: If you are going to use ThreadLocale, you have to take care of some usage risks when merging threads (as in server applications). You may get memory leaks or information leaks between requests. I won't go into too much detail about this because... The article “ How to shoot yourself in the foot with ThreadLocale ” demonstrates well how this can happen. 4. Using synchronization Another way to provide thread-safe access to objects is through synchronization. If we synchronize all accesses to a reference, then only one thread object will access it at a given time. We will discuss this in future posts. 5. Conclusion We looked at several methods that allow you to build simple objects that can be accessed by multiple threads. It is much more difficult to prevent multi-threaded errors if an object can have multiple states. On the other hand, if an object can have only one state or none, we don't have to worry about multiple threads accessing the object at the same time. The original is here . 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 ...
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION