Java concurrent programming (2) immutability of objects and secure release objects

Source: Internet
Author: User

Java concurrent programming (2) immutability of objects and secure release objects
1. immutability

Another way to meet synchronization needs is to use Immutable objects ). So far, we have introduced many problems related to atomicity and visibility, such as getting invalid data, losing update operations, or simply finding an object in an inconsistent state, it is related to the multi-threaded view accessing the same variable state at the same time. If the object state does not change, these problems and complexity will naturally disappear.

If the status of an object cannot be modified after it is created, the object becomes an immutable object. The thread-safe type is one of the inherent attributes of an immutable object. Their immutability condition isCreate a constructorAs long as their status does not change, these immutability conditions can be maintained.

Immutable objects are simple. They only have one State, and the State is controlled by the constructor. The most difficult part in programming is to judge the possible states of complex objects. However, it is easy to judge the state of an unchangeable object.

Although neither the Java specification nor the Java memory model provides a formal definition of immutability, immutability does not mean to declare all the fields in the object as the final type, even if all the fields in the object are of the final type, this object is still variable, because the reference to the variable object can be saved in the final type domain.

Objects are unchangeable when the following conditions are met:

After an object is created, its status cannot be changed. All objects of the object and objects of the final type are created correctly (this does not escape during creation). We will analyze the following class.
@Immutablepublic final class ThreeStooges {    private final Set
 
   stooges = new HashSet
  
   ();    public ThreeStooges() {        stooges.add("Moe");        stooges.add("Larry");        stooges.add("Curly");    }    public boolean isStooge(String name) {        return stooges.contains(name);    }}
  
 

You can still use mutable objects to manage their statuses within an immutable object, as shown in ThreeStooges. Although the Set object that saves the name is variable, we can see from the ThreeStooges design that it cannot be modified after the Set object is constructed. Stooges is a reference variable of the final type, so all object states are accessed through a final domain. The last requirement is "correct Object Construction", which is easily met, because constructors allow this reference to be accessed by code other than constructors and callers.

Because the state of the program is constantly changing, you may think that there are not many immutable objects to be used, but this is not the case. There is a difference between "immutable object" and "immutable object reference. The state of the program stored in the immutable object can still be updated, that is, the original immutable object can be replaced by an instance that saves the new state.

Final domain

The final keyword can be regarded as a restricted version of the const Mechanism in C ++ and is used to construct immutable objects. Final fields cannot be modified (however, if the objects referenced by the final domain are variable, these referenced objects can be modified ). However, in the Java memory model, the final domain also has special semantics. The final domain ensures the security of the initialization process and allows unrestricted access to immutable objects without synchronization when sharing these objects.

Note: Personally, once the final field is initialized and the constructor does not pass this reference, the final field value (intra-domain variable visibility, similar to volatile), and its external visibility status will never change. Its security is the simplest and purest.

Note: even if the object is variable, you can declare some fields of the object as the final type.Simplify status judgmentTherefore, limiting the variability of an object is equivalent to limiting the set of possible states of the object. Objects that contain only one or two mutable states are still simpler than objects that contain multiple mutable states. By declaring the domain as the final type, it is equivalent to telling the maintenance personnel that these domains will not change.

Just as "unless higher visibility is needed, declaring all ELE. Me domains as private domains" [J. Item 12] is a good habit, "unless a domain is needed to be mutable, it should be declared as a final domain" is also a good habit.

Example: Use the Volatile type to publish immutable objects

As we have mentioned earlier, volatile can be used to ensure the visibility of the domain, but cannot guarantee the atomicity of variable operations. More accurately, it can only ensure that read and write operations are atomic, however, the atomicity of auto-increment I ++ and other operations cannot be guaranteed.

In the previous UnsafeCachingFactorizer class, we tried to use two AtomicReferences variables to save the latest values and their factorization results. However, this method is not thread-safe, because we cannot read or update these two values simultaneously in atomic mode. Similarly, using volatile variables to save these values is not thread-safe. However, in some cases, immutable objects can provide a weak form of atomicity.

The Factorization Servlet performs two atomic operations: update the cache results, and determine whether to directly read the factorization results in the cache by determining whether the cached value is equal to the requested value. Whenever you need to perform an atomic operation on a group of related data, you can consider creating an unchangeable class to include the data, such as OneValueCache.

@ Immutableclass OneValueCache {private final BigInteger lastNumber; private final BigInteger [] lastFactors;/*** if Arrays is not used in the constructor. copyOf () method, so the inmutable object in the domain can be changed by the out-of-domain code * So OneValueCache is not immutable. */Public OneValueCache (BigInteger I, BigInteger [] factors) {lastNumber = I; lastFactors = Arrays. copyOf (factors, factors. length);} public BigInteger [] getFactors (BigInteger I) {if (lastNumber = null |! LastNumber. equals (I) return null; else return Arrays. copyOf (lastFactors, lastFactors. length );}}

When you access and update multiple variables, you can save these variables in an immutable object. If it is a mutable object, the lock must be used to ensure atomicity. If it is an immutable object, after the thread obtains a reference to this object Don't worry that another thread will modify the object status. To update these variables, you can create a new container object, but other threads that use the original object will still see that the object is in the consistent state.

In VolatileCachedFactorizer, OneValueCache is used to save the cached value and its factor. We declare OneValueCache as volatile, so that when a thread sets cache to reference a new OneValueCache, other threads will immediately see the new cached data.

@ Brief class implements Servlet {private volatile OneValueCache cache = new OneValueCache (null, null); public void service (ServletRequest req, ServletResponse resp) {BigInteger I = extractFromRequest (req ); bigInteger [] factors = cache. getFactors (I); if (factors = null) {factors = factor (I); cache = new OneValueCache (I, factors); // declare it as volatile, prevents Command Re-sorting and ensures visibility} encodeIntoResponse (resp, factors );}}

Operations related to the cache do not interfere with each other, because OneValueCache is immutable and will only be accessed once in each corresponding code path. By using container objects that contain multiple state variables to maintain immutability conditions, and using a volatile type reference to ensure visibility, so that Volatile Cached Factorizer remains thread-safe without explicitly using the lock.

Ii. Secure release

So far, we have focused on how to ensure that objects are not published, such as sealing objects inside a thread or another object. Of course, in some cases, if we want to share objects among multiple threads, we must ensure secure sharing. However, if you just save the object reference to the public domain like the following program, it is not enough to safely publish this object.

// Insecure public Holder holder; public void initialize () {holder = new Holder (42 );}

You may wonder why this seemingly normal example fails to run. Due to visibility issues, the Holder objects seen by other threads will be in an inconsistent state, even if they have correctly constructed immutability conditions in their constructors. This incorrect release results in other threads seeing objects that have not yet been created.

Incorrect release: The correct object is damaged.

You cannot expect an object that has not yet been fully created to have integrity. A thread that observes this object will see that the object is in an inconsistent state, and then the object state changes suddenly, even if the thread has not modified it after the object is published. In fact, if the Holder in the following program uses the unsafe release method in the previous program, the other thread will throw AssertionError when calling assertSanity.

public class Holder {    private int n;    public Holder(int n) { this.n = n; }    public void assertSanity() {        if (n != n)            throw new AssertionError("This statement is false.");    }}

Because synchronization is not used to ensure that the Holder object is visible to other threads, the Holder is called "not correctly published ". There are two problems in objects that are not correctly published.

FirstIn addition to the thread of the published object, other threads can seeThe Holder field is an invalid value.Therefore, an empty reference or an old value is displayed.

HoweverWorse, the thread sees that the value referenced by Holder is the latest, but the value in the Holder status is invalid. It is even more unpredictable that a thread gets an invalid value when it reads the domain for the first time, and an updated value when it reads the domain again. This is why assertSainty throws AssertionError.

If there is not enough synchronization, some strange things will happen when data is shared among multiple threads.

Immutable object and initialization Security

Since an immutable object is a very important objectThe Java memory model provides a special initialization security guarantee for the sharing of immutable objects.. We already know that even if an object's reference is visible to other threads, it does not mean that the object state is visible to the thread that uses the object. To ensure that the object state shows a consistent view, you must use synchronization.

On the other hand, even if you do not use synchronization when releasing a reference to an immutable object, you can still securely access the object. In order to maintain this initialization security guarantee, all non-mutable requirements must be met: the State cannot be modified, all domains are of the final type, and the correct construction process must be met. (If the Holder object is unchangeable, AssertionError is not thrown in assertSanity even if the Holder is not correctly published .)

Any thread can securely access unchangeable objects without additional synchronization, even if synchronization is not used when these objects are published.

This guarantee also extends to all final fields in the correctly created object. You can also securely access final domains without additional synchronization. However, if the final type domain points to a mutable object, you still need to synchronize the state of the object to which the domain points.

Common modes of secure Publishing

A mutable object must be released in a safe way, which usually means that synchronization must be used when the thread that publishes and uses the object. Now, we will focus on how to ensure that the thread using the object can see that the object is in the published state, and later on how to modify its visibility after the object is published.

Securely publish an object. The application and state of the object must be visible to other threads at the same time. You can securely publish a correctly constructed object in the following ways:

Initialize an object reference in the static initialization function. Save the object application to the volatile type domain or the AtomicReferance object. Save the object reference to the final type domain of a correctly constructed object and reference the object. save to a locked domain. Synchronization within a thread-Safe Container means that when an object is put into a container, such as a Vector or synchronizedList, the above last requirement is met. If thread A puts object X into A thread-Safe Container, and then thread B reads the object, it can ensure that B sees the X state set by, even if the application code that reads/writes X does not contain explicit synchronization. Although Javadoc does not provide a clear description on this topic, the container class in the thread Security Library provides the following security release guarantees: put a key or value in Hashtable, synchronizedMap, or ConcurrentMap, it can be securely published to any thread that accesses it from these containers (whether directly or through the iterator) add an element to a Vector, CopyOnWriteArrayList, CopyOnWriteArraySet, synchronizedList, or synchronizedSet, this element can be securely published to any thread accessing the element from these containers by placing an element in BlockingQueue or concurrent1_queue, this element can be securely published to any thread that accesses this element from these queues.

Other data transmission mechanisms (such as Future and Exchanger) in the class library can also implement secure Publishing. When introducing these mechanisms, we will discuss their secure publishing functions.

Generally, to publish a static object, the simplest and safest way is to use a static initiator:
Public static Holder holder = new Holder (42 );

The static initiator is executed by the JVM in the class initialization phase. Because a synchronization mechanism exists in the JVM, any object initialized in this way can be safely released [JLS 12.4.2].

Fact immutable object

If the object is not modified after the release, it is sufficient for other threads that securely access the object without extra synchronization. All secure publishing mechanisms ensure that when the object reference is visible to all threads that access the object, the state of the object when it is published is visible to all threads, and if the object state does not change, it is enough to ensure that any access is safe.

If the object is technically changeable but its status does not change after release, the object is called"Fact immutable object(Effectively Immutable Object )". These objects do not need to meet the strict definition of immutable. After these objects are published, the program simply regards them as immutable objects. Using fact immutable objects can not only simplify the development process, but also improve performance due to reduced synchronization.

Without additional synchronization, any thread can safely use the fact immutable objects that have been securely published.

For example, Date itself is variable, but if it is used as an immutable object, the use of the lock can be omitted when the Date object is shared among multiple threads. Assume that you need to maintain a Map object, which saves the recent logon time of each user:
Public Map LastLogin = Collections. synchronizedMap (new HashMap ());

If the value of the Date object is not changed after it is put into the Map, the synchronization mechanism in synchronizedMap is enough to ensure that the Date value is securely published, no additional synchronization is required when accessing these Date values.

Variable object

If the object can be modified after construction, the security release can only ensure the visibility of the "release at the time" status. For a mutable object, you must not only use synchronization when releasing the object, but also use synchronization each time the object is accessed to ensure the visibility of subsequent modification operations. To securely share mutable objects, these objects must be securely published and must be thread-safe or protected by a lock.

The release requirement of an object depends on its variability:

The fact that an immutable object can be published through any mechanism cannot be changed. A mutable object must be published securely, it must be a thread-safe or shared object protected by a lock.

When obtaining a reference to an object, You Need To Know What operations can be performed on this reference. Do I need to get a lock before using it? Can I modify its status or read it only? Many concurrency errors are caused by failing to understand the "established rules" of shared objects. When releasing an object, you must explicitly describe the object access method.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.