Java threads and makeup Util.Concurrent research (2)

xiaoxiao2021-03-06  38

From http://www-900.ibm.com/developerWorks/cn/java/J-JTP 11234/1. The Dark Age before the new atomic class is born: before JSDK 1.5, if you don't use the machine code, you can't use Java. Language writing unlocked algorithms. After adding an atomic variable class in java.util.concurrent, this situation has changed. Please follow the parallel expert Brian Goetz to learn how these new classes develop highly scalable unstormless algorithms using Java language. You can be in this article

forum

Sharing your views from author or other readers. (You can also access the discussion by clicking on the top of the article or the discussion link on the bottom.)

Fifteen years ago, multiprocessor system is a highly dedicated system, which costs hundreds of thousands of dollars (most of which have two to four processors). Now, multiprocessor systems are very cheap, and there are many quantities, and almost every major microprocessor has built-in support, many of which support dozens or hundreds of processors.

To use multiprocessor systems, you usually need to use multi-threaded constructive applications. However, as anyone who writes concurrent applications can tell you, to get good hardware utilization, just simply dividing the job in multiple threads is not enough, but also ensure that the thread is indeed working most of the time, and Not waiting for more work, or waiting to lock the shared data structure.

Problem: Coordination between threads If there is no coordination between threads, there is almost no task can be truly parallel. Take the thread pool as an example, where the tasks performed are usually independent of each other. If the thread pool uses a public work queue, delete elements from the work queue or add an element to the work queue must be threads, and this means to coordinate access to the head, tail, or between the node link pointers. It is this coordination that causes all problems.

Standard method: Lock in the Java language, coordinate the traditional way to access the shared field is to use synchronization to ensure that all access to the shared field is completed, and there is an appropriate lock. By synchronizing, you can determine that all threads with locking of the locking of a set of given variables will have exclusive access to these variables, and the other threads will be viewed to these variables. Make changes. The drawback is that if the lock competition is too powerful (threads often require this lock when there is a lock), it will impair throughput because the synchronization of competition is very expensive. (Public Service Announcement: For modern JVM, no competitive synchronization is now very cheap.

Another problem based on the lock-based algorithm is that if the latency has a locked thread (because the page error, the scheduled delay, or other unexpected delay is delayed), there is no requirement that the locking thread can continue to run.

Variable variables can also be used to store shared variables than the lower cost than synchronous, but they have limitations. Although other variables can immediately see the written to variable variables, it cannot present a read-modification-write order, which means (for example) variable variables can not be used reliably Slimming the locked) or counter.

This will expose get (), increment (), and Decrement () operations if the counter class is implemented. Listing 1 shows how to use lock (synchronous) to implement such an example. Note that all methods are needed to synchronize get (), so that the class is a class that thread security, ensuring that no update information is lost, all threads see the latest values ​​of the counter.

Listing 1. Synchronous counter class

Public class synchronizedcounter {

Private Int value; public synchronized int getValue () {return value;}

Public synchronized int increment () {return value;}

Public synchronized int decrement () {return - vialue;}

}

INCREMENT () and DECREMENT () operations are atomic read-modified-write operations, in order to secure the counter, you must use the current value, add a value, or write new value, all of which are considered an action, Other threads cannot interrupt it. Otherwise, if the two threads are trying to perform increase in simultaneous implementation, the unfortunate crossings of the operation will cause the counter to be only implemented once, not two times. (Note that this operation cannot be done reliably by making a variable variable becomes variable.)

Among the concurrent algorithm is displayed - modified - write combinations. The code in Listing 2 achieves a simple mutual exclusion, and the acquire () method is also the atomic read - modification - write operation. To get mutually exclusive, you must ensure that no other people have this mutual exclusion (Curowner = Thread.currentThread ()), then record the fact that you have the mutual extension (Curowner = Thread.currentThread ()), all of this makes other threads In the middle and modify the Curowner Field.

Listing 2. Synchronous mutual exclusion

Public class synchronizedmutex {

PRIVATE THREAD CUROWNER = NULL;

Public synchronized void acquire () throws interruptedException {

IF (thread.interrupted ()) throw new interface ();

While (Curowner! = NULL)

Wait ();

Curowner = thread.currentthread ();

}

Public synchronized void release () {

IF (Curowner == Thread.currentthread ()) {

Curowner = NULL;

NOTIFY ();

Else

Throw New IllegalStateException ("Not Owner of Mutex");

}

}

The counter classes in Listing 1 can work reliably, and can be performed well when competition is small or without competition. However, this will greatly damage performance when competition is, because JVM uses more time to schedule threads, manage competition, and wait line queues, and actually work (such as adding counters). You can recall the chart in the column of the previous month, which shows how the throughput will decrease once multiple threads use synchronous competition. Although this column illustrates how new ReentrantLock classes can be more scalable to synchronize, for some problems, there is a better solution.

Lock issues are locked if a thread is trying to get a lock that has already had other threads, and the thread will be blocked until the lock is available. This method has some significant disadvantages, including any other operation when the thread is blocked to wait for the lock. If the blocking thread is a high priority task, then the program may cause a very bad result (called the risk of priority inversion).

There are some other hazards that use locks, such as deadlocks (when multiple locked in inconsistencies). There is no such danger, and the lock is only the relative coarse granular coordination mechanism, and it is also very suitable for managing simple operations, such as adding counters or update mutual exclusive owners. If there is a finer granular mechanism to reliably manage the concurrency update of individual variables, it will be better; there is such a mechanism in most modern processors. 2. Great inventions - New Atomic: Hardware Synchronous Original Look As mentioned before, most modern processors contain support for multi-processing. Of course, this support includes multiprocessors to share external devices and primary memory, and it usually includes special requirements for the increase in the instruction system to support multiple processing. In particular, almost every modern processor has instructions to update the shared variable by detecting or blocking the concurrent access to other processors.

Compare and exchange (CAS) supports the first processor that supports concurrency provides an atomic test and sets the operation, which is usually run on the unit. The most common method used by the current processor (including Intel and SPARC processors) is to implement primitives called comparison and conversion or CAS. (In the Intel processor, compare and exchange CMPXCHG series implementation through instructions. The PowerPC processor has a pair of instructions named "Load and retain" and "conditional storage", which is similar to the powerpc processor, In addition to the first instruction called "loading link".)

The CAS operation contains three operands-memory locations (V), expected original values ​​(a) and new value (b). If the value of the memory location matches the expected original value, the processor will automatically update the location value to a new value. Otherwise, the processor does not do anything. Either case, it returns the value of this location before the CAS command. (Some of CAS will return only whether CAS is successful without extracting the current value.) CAS effectively illustrates "I think the location V should contain value a; if this value is included, B will put B in this position; Otherwise, don't change this location, just tell me this location now. "

The way CAS is usually used to synchronize is to read the value A from the address V, perform multi-step computing to obtain a new value B, and then change the value of V from A to B using CAS. If the value of V is not changed, the CAS operation is successful.

Similar to the CAS command allows the algorithm to perform read-modification - write operation without fear other threads simultaneously modify the variable, because if other thread modifies the variable, CAS detects it (and failed), the algorithm can recalculate the operation. Listing 3 illustrates the behavior of the CAS operation (instead of performance characteristics), but the value of CAS can be implemented in hardware and is extremely magnitude (in most processors):

Listing 3. Description Compare and exchange behavior (not performance) code

Public class simulatedcas {

Private int value;

Public synchronized int getValue () {return value;}

Public synchronized int compareAndSwap (int.com) {{{{

IF (value == expectedValue)

Value = newValue;

Return Value;

}

}

Using the CAS implementation counter based CAS-based concurrent algorithm is called unlocking algorithms because threads do not have to wait for locking (sometimes referred to as mutual exclusion or critical parts, depending on the terminology of the thread platform). Regardless of the success of CAS operation or failure, in any case, it is done in a presented time. If the CAS fails, the caller can retry the CAS operation or take other suitable operations. Listing 4 shows the rewritable counter class to use the CAS alternative to lock: Listing 4. Using comparison and exchange implementation counters

Public class cascounter {

Private Simulatedcas Value;

Public Int getValue () {

Return Value.getValue ();

}

Public int increment () {

Int OldValue = Value.getValue ();

While (Value.comPareAndswap (OldValue, OldValue 1)! = OldValue)

OldValue = Value.getValue ();

RETURN OLDVALUE 1;

}

}

Unlocked and no wait algorithm will continue to operate while each thread is freely delayed (or even fail), it can be said that the algorithm is unwrapped. In contrast to this, the unlocking algorithm requires that only a thread always performs operations. (Another definition that is not waiting to ensure that each thread correctly calculates your own operation in its limited step, regardless of the operation, timing, crossing or speed of other threads. This limitation can be a function of the thread number in the system; For example, if there is 10 threads, each thread performs a cascounter.increment () operation, the worst case, each thread will have to try up to nine times, can be added.)

In the past 15 years, people have studied a lot of research without waiting and unlocking algorithms (also known as non-blocking algorithms), and many universal data structures have discovered no blocking algorithm. The unusable algorithm is widely used in operating systems and JVM levels, such as threads and process schedules. Although their achievements are more complicated, they have many advantages over the lock-based alternative algorithm: avoiding priority inversion and deadlocks, etc. Parallel mechanism, etc.

The atomic variable class is before JDK 5.0, if the unit is not used, it cannot be written in Java language, unlocked algorithms. This situation has changed after adding an atomic variable class in a java.util.concurrent.atomic package. All atomic variable classes are disclosed and set primitive (similar to comparison), these primitives are the fastest native structure available on the platform (compare and exchange, load link / condition storage, worst case) It is a rotary lock) to achieve. ATOMICINTEGER; atomicinteger; atomic1ger; atomic1ger; atomic10; atomic, atomic; long; reference; and atomic marker reference and stamp reference class Update a pair of values).

The atomic variable class can consider the generalization of the volatile variable, which extends the concept of variable variables to support the comparison of atom conditions and set up updates. Reading and writing an atomic variable with the access and write access to variable variables have the same access semantic.

Although the atomic variable class surface appears to be the same as the SYNCHRONIZEDCUNTER example in Listing 1, it is similar to the surface. Under the surface, the operation of the atomic variable becomes hardware primitive for concurrent access, such as comparative and exchanged. Fine granularity means that a lightweight adjustment of scalability with competitive concurrent applications is to reduce the particle size of the locked object used, and I hope that more lock requests are not competitive from competition. The same result can be obtained from the lock-in converted to the atomic variable, and the operation of the competitive operation is less, thereby increasing the throughput by switching to a more fine granularity coordination mechanism. 3. How to use new atomic classes in JSDK1.4: Since I have not installed JSDK1.5, there is no way to enjoy happiness in Util.concurrent. However, it is possible to use a compatible bag-Java 1.4 Backport of JSR 166 (java.util.concurrent) that support JSDK1.4, this package is maintained by Mr. Dawid Kurzyniec. Access to http://www.mathcs.emory.edu/dcl/util/backport-util-concurrent/ to get details. This package implements all of the important features of Util.Concurrent, but there is still no lacking in some ways. As for the reasons, it will not say that JVM1.4 is not supported. The following is the current Java 1.4 backport of 166 features supported JSR: All JSR 166 executors, utilities, and everything related (thread pools, FutureTask, scheduled tasks and executors, etc) Locks: ReentrantLock, Semaphore, ReentrantReadWriteLock (see remarks below), Conditions Queues: synchronous, array, linked, delay, and priority queues Atomics: everything except reflection-based updaters Other concurrency utils: CountDownLatch, CyclicBarrier Collections: ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet below for the current Java 1.4 backport of JSR 166 does not support features : in ConditionMethod long awaitNanos (long nanosTimeout) is not supported, since the emulation can not reliably report remaining times with nanosecond precision Thus, it propably would be too dangerous to leave the emulated method in the Condition interface However, the method is still available.. For Those Who Know What They Are Doing, In The Util.concurrent.helpers.Utils Class.

Methods boolean await (timeout) and boolean awaitUntil (Date), called on conditions obtained from locks, may sometimes wake up spuriously. This is allowed by the Condition specification. However, it causes them to occassionally fail TCK unit tests. It has been acknowledged by Doug Lea, JSR 166 expert group lead, that these tests are too rigorous; they will be relaxed in future releases The bottom line is that this implementation does conform to the specification in that respect.In ReentrantLockThe following monitoring methods are not supported:. Boolean Haswaiters (Condition), INT GetWaitQuelength (Condition), Collection GetWaitingThreads (Condition).

The Following Monitoring Methods Are Supported Only for Fair Locks: Boolean Hasqueueuedthreads (), Int getQuelength (), Collection getQueuedThreads (), Boolean isqueued ().

In ReentrantReadWriteLockThe current backport implementation is based on dl.util.concurrent class ReentrantWriterPreferenceReadWriteLock, and thus slightly departs from java.util.concurrent that does not specify acquisition order but allows to enable / disable fairness. The backport implementation does not have a single-parameter constructor allowing to specify fairness policy;. it always behaves like writer-preference lock with no fairness guarantees bacause of these characteristics, this class is compliant with JSR 166 specification of non-fair reentrant read-write locks, while the exact semantics of fair locks Are Not Supported (and the appropriate constructor is missing).

Also, the following instrumentation and status methods are not supported: Collection getQueuedWriterThreads (), Collection getQueuedReaderThreads (), boolean hasQueuedThreads (), boolean hasQueuedThread (Thread), Collection getQueuedThreads (), boolean hasWaiters (Condition), int getWaitQueueLength (Condition), Collection GetWaitingThreads (Condition) .IN SemaphoreAtomic Multi-Acquires: Tryacquire (int Permits) and tryacquire (int Permits, long timeout, timeunit unit) Are not supported.

Platform-level functionalityTo emulate System.nanoTime (), the method nanoTime () is provided in the class dl.util.concurrent.helpers.Utils. On Java 1.4.2, it attempts to via use high-precision timer sun.misc. Perf (Thanks to Craig Mattocks for Suggesting this). ON Older Java Platforms, or when supported, IT Falls Back to System.currentTimeMillis ().

Class threadhelpers (added in 1.0_01) is provided to emulate certain assects of thread.uncaughtexceptionhandler.

Note on nanosecond precision timingThe backport strives to honor nanosecond timeouts, if such are requested, by using two-parameter variant of Object.wait (). Note, however, that most Java platforms before 5.0 will round up the timeout to full milliseconds anyway.

Low-Level Concurrency Classesthe Following Classes Are Not Supported: Locksupport, AbstractQueuedSynchronizer.

Atomic Utilitiesthe Following "Atomic" Utilities Are Not Supported: Atomic [Integer, Long, Reference] FieldUpdater.

Collection Classes Are Not Supported: LinkedList, ConcurrentLinkedQueue. In general, you can basically meet my requirements.

转载请注明原文地址:https://www.9cbs.com/read-69432.html

New Post(0)