Thinking logic of computer programs (70) and thinking 70

Source: Internet
Author: User

Thinking logic of computer programs (70) and thinking 70

Starting from this section, we will discuss the content in Java concurrency toolkit java. util. concurrent. This section first introduces the most basic atomic variables and the principles and thinking behind them.

Atomic variable

What is an atomic variable? Why do we need them?

In the synchronized section, we introduced a Counter class that uses the synchronized keyword to ensure atomic update operations. The Code is as follows:

public class Counter {    private int count;    public synchronized void incr(){        count ++;    }        public synchronized int getCount() {        return count;    }}

For the count ++ operation, synchronzied is too costly. You need to obtain the lock first, and then release the lock. If you cannot obtain the lock, you have to wait, there will also be thread context switching, which requires costs.

In this case, atomic variables can be used instead. The basic types of atomic variables in the Java package include:

  • AtomicBoolean: Atomic Boolean Type
  • AtomicInteger: Atomic Integer type
  • AtomicLong: Atomic Long type
  • AtomicReference: Atomic reference type

This is the main class we will introduce. In addition to these four classes, we will also briefly introduce some other classes.

For Integer, Long, and Reference types, there are also corresponding array types:

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

To update fields in an object in atomic mode, the following classes are available:

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

AtomicReference has two similar classes, which are easier to use in some cases:

  • AtomicMarkableReference
  • AtomicStampedReference

You may find that there are no atomic variables for char, short, float, and double types? It is probably used less. If needed, you can convert it to int/long, and then use AtomicInteger or AtomicLong. For example, for float, you can use the following method to convert each other with int:

public static int floatToIntBits(float value)public static float intBitsToFloat(int bits);

Next, let's take a look at several basic atomic types, starting with AtomicInteger.

AtomicInteger

Basic usage

AtomicInteger has two constructor methods:

public AtomicInteger(int initialValue)public AtomicInteger()

The first constructor is given an initial value, and the second initial value is 0.

You can directly obtain or set the value in AtomicInteger:

public final int get()public final void set(int newValue) 

It is called an atomic variable because it contains some methods to implement combined operations in atomic mode, such:

// Obtain the old value in atomic mode and set the new value public final int getAndSet (int newValue) // obtain the old value in atomic mode and Add 1 public final int getAndIncrement () to the current value () // obtain the old value in atomic mode and subtract 1 public final int getAndDecrement () from the current value () // obtain the old value in atomic mode and add deltapublic final int getAndAdd (int delta) to the current value) // Add 1 to the current value in atomic mode and get the new value public final int incrementAndGet () // subtract 1 from the current value in atomic mode and obtain the new value public final int decrementAndGet () // Add delta to the current value in atomic mode and obtain the new value public final int addAndGet (int delta)

The implementation of these methods depends on another public method:

public final boolean compareAndSet(int expect, int update)

This is a very important method. Compare and set it. We will refer to it as CAS in the future. This method implements the following functions in atomic mode: if the current value is equal to CT, update is updated; otherwise, update is not performed. If the update is successful, true is returned; otherwise, false is returned.

AtomicInteger can be used as a counter in a program. Multiple Threads update concurrently and always implement correctness. Let's look at an example:

public class AtomicIntegerDemo {    private static AtomicInteger counter = new AtomicInteger(0);    static class Visitor extends Thread {        @Override        public void run() {            for (int i = 0; i < 100; i++) {                counter.incrementAndGet();                Thread.yield();            }        }    }    public static void main(String[] args) throws InterruptedException {        int num = 100;        Thread[] threads = new Thread[num];        for (int i = 0; i < num; i++) {            threads[i] = new Visitor();            threads[i].start();        }        for (int i = 0; i < num; i++) {            threads[i].join();        }        System.out.println(counter.get());    }}

The program output is always correct, which is 10000.

Basic Principles and thinking

The usage of AtomicInteger is simple and straightforward. How does it implement it? Its main internal members are:

private volatile int value;

Note that its declaration carries volatile, which is required to ensure memory visibility.

Most of its update methods are implemented similarly. Let's look at the incrementAndGet method, and its code is:

public final int incrementAndGet() {    for (;;) {        int current = get();        int next = current + 1;        if (compareAndSet(current, next))            return next;    }}

The main body of the Code is an endless loop. First, the current value is obtained, the expected value is calculated next, and then the CAS method is called to update the Code. If the current value does not change, the code is updated and a new value is returned, otherwise, the loop continues until the update is successful.

Compared with synchronized locks, this atomic update method represents a different way of thinking.

Synchronized is pessimistic. It assumes that updates are likely to conflict with each other. Therefore, the system obtains the lock and obtains the lock before updating it. The update logic of atomic variables is optimistic. It assumes that there are few conflicts, But CAS update is used to detect conflicts. If there is a conflict, it doesn't matter, just try again.

Synchronized indicates a blocking algorithm. When the lock is not obtained, it enters the lock wait queue and waits for other threads to wake up, causing context switching overhead. The update logic of an atomic variable is non-blocking. When an update conflict occurs, it retries, does not block, and does not have context switching overhead.

For most simple operations, the performance of this optimistic non-blocking mode is much higher than that of the pessimistic blocking mode in the case of low concurrency or high concurrency.

Atomic variables are relatively simple, but for complex data structures and algorithms, non-blocking methods are often difficult to implement and understand. Fortunately, some non-blocking containers have been provided in Java concurrent packages. We only need to use them, for example:

  • Concurrentincluqueue and concurrentincludeque: non-blocking concurrent queue
  • ConcurrentSkipListMap and ConcurrentSkipListSet: non-blocking concurrent Map and Set

We will introduce these containers in subsequent chapters.

But how is compareAndSet implemented? Let's look at the Code:

public final boolean compareAndSet(int expect, int update) {    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

It calls the compareAndSwapInt method of unsafe. What is unsafe? Its type is sun. misc. Unsafe and is defined:

private static final Unsafe unsafe = Unsafe.getUnsafe();

It is Sun's private implementation. From the perspective of its name, it also indicates "insecure". Generally, applications should not be used directly. In principle, general computer systems directly support CAS commands at the hardware level, and Java implementation will use these special commands. From the perspective of the program, we can regard compareAndSet as the basic operation of the computer, just accept it directly.

Implement lock

Based on CAS, in addition to implementing optimistic non-blocking algorithms, it can also be used to implement pessimistic blocking algorithms, such as locks. In fact, all blocking tools, containers, and Algorithms in Java concurrent packages are also CAS-based (however, some other support is required ).

How can this problem be achieved? Let's demonstrate a simple example. Use AtomicInteger to implement a lock MyLock. The Code is as follows:

public class MyLock {    private AtomicInteger status = new AtomicInteger(0);    public void lock() {        while (!status.compareAndSet(0, 1)) {            Thread.yield();        }    }    public void unlock() {        status.compareAndSet(1, 0);    }}

In MyLock, status indicates the lock status, 0 indicates that the lock is not locked, 1 indicates the lock, lock ()/unlock () uses the CAS method to update, lock () the blocking effect is achieved only after the update is successful. However, this blocking method consumes too much CPU and is more efficient. MyLock is only used to demonstrate basic concepts. In actual development, classes such as ReentrantLock in Java and package should be used.

AtomicBoolean/AtomicLong/AtomicReference

The usage and principles of AtomicBoolean/AtomicLong/AtomicReference are similar to those of AtomicInteger.

AtomicBoolean

AtomicBoolean can be used to represent a flag in a program. Its atomic operation methods include:

public final boolean compareAndSet(boolean expect, boolean update)public final boolean getAndSet(boolean newValue) 

In fact, AtomicBoolean uses an int type value internally, which uses 1 to indicate true and 0 to indicate false. For example, the CAS method code is:

public final boolean compareAndSet(boolean expect, boolean update) {    int e = expect ? 1 : 0;    int u = update ? 1 : 0;    return unsafe.compareAndSwapInt(this, valueOffset, e, u);}

AtomicLong

AtomicBoolean can be used to generate a unique serial number in a program. Its method is similar to that of AtomicInteger. Its CAS method calls another unsafe method, such:

public final boolean compareAndSet(long expect, long update) {    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}

AtomicReference

AtomicReference is used to update complex types in atomic mode. It has a type parameter, and the referenced type must be specified during use. The following code demonstrates the basic usage:

public class AtomicReferenceDemo {    static class Pair {        final private int first;        final private int second;        public Pair(int first, int second) {            this.first = first;            this.second = second;        }        public int getFirst() {            return first;        }        public int getSecond() {            return second;        }    }    public static void main(String[] args) {        Pair p = new Pair(100, 200);        AtomicReference<Pair> pairRef = new AtomicReference<>(p);        pairRef.compareAndSet(p, new Pair(200, 200));        System.out.println(pairRef.get().getFirst());    }}

The CAS method of AtomicReference calls another unsafe method:

public final boolean compareAndSet(V expect, V update) {    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);}

Atomic Array

An atomic array is used to update every element in an array in an atomic way. We will take AtomicIntegerArray as an example to briefly introduce it.

It has two constructor methods:

public AtomicIntegerArray(int length)public AtomicIntegerArray(int[] array) 

The first creates an empty array with a length. The second one accepts an existing array, but does not directly operate on the array. Instead, it creates a new array, just copying the content in the parameter array to the new array.

Most atomic update Methods in AtomicIntegerArray contain array index parameters, such:

public final boolean compareAndSet(int i, int expect, int update)public final int getAndIncrement(int i)public final int getAndAdd(int i, int delta)

The first parameter I is the index. Let's look at a simple example:

public class AtomicArrayDemo {    public static void main(String[] args) {        int[] arr = { 1, 2, 3, 4 };        AtomicIntegerArray atomicArr = new AtomicIntegerArray(arr);        atomicArr.compareAndSet(1, 2, 100);        System.out.println(atomicArr.get(1));        System.out.println(arr[1]);    }}

Output:

1002

FieldUpdater

FieldUpdater allows you to easily update fields in an object in atomic mode. fields do not need to be declared as atomic variables. FieldUpdater is implemented based on the reflection mechanism. We will introduce reflection in subsequent sections, here is a brief introduction to its usage. See the Code:

public class FieldUpdaterDemo {    static class DemoObject {        private volatile int num;        private volatile Object ref;        private static final AtomicIntegerFieldUpdater<DemoObject> numUpdater            = AtomicIntegerFieldUpdater.newUpdater(DemoObject.class, "num");        private static final AtomicReferenceFieldUpdater<DemoObject, Object>            refUpdater = AtomicReferenceFieldUpdater.newUpdater(                    DemoObject.class, Object.class, "ref");        public boolean compareAndSetNum(int expect, int update) {            return numUpdater.compareAndSet(this, expect, update);        }        public int getNum() {            return num;        }        public Object compareAndSetRef(Object expect, Object update) {            return refUpdater.compareAndSet(this, expect, update);        }        public Object getRef() {            return ref;        }    }    public static void main(String[] args) {        DemoObject obj = new DemoObject();        obj.compareAndSetNum(0, 100);        obj.compareAndSetRef(null, new String("hello"));        System.out.println(obj.getNum());        System.out.println(obj.getRef());    }}

The DemoObject class has two members: num and ref, which are declared as volatile but not atomic variables. However, DemoObject provides the atomic update method compareAndSet, which is implemented by FieldUpdater corresponding to the field, fieldUpdater is a static member. It is obtained through the newUpdater factory method. The parameters required by newUpdater include the type, field name, and the specific type to be referenced for the reference type.

ABA Problems

There is an ABA problem when using CAS to update. This problem means that A thread starts to see A value and then uses CAS to update it, its actual expectation is that it will be updated only when no other threads have modified it, but the Common CAS cannot, because other threads have modified it in this process, for example, change it to B first, then change to.

ABA is not a problem related to the logic of the program. If it is a problem, a solution is to use AtomicStampedReference and add a timestamp while modifying the value, the CAS method is declared as follows:

public boolean compareAndSet(    V expectedReference, V newReference, int expectedStamp, int newStamp) 

For example:

Pair pair = new Pair(100, 200);int stamp = 1;AtomicStampedReference<Pair> pairRef = new AtomicStampedReference<Pair>(pair, stamp);int newStamp = 2;pairRef.compareAndSet(pair, new Pair(200, 200), stamp, newStamp);

AtomicStampedReference must modify two values at the same time in compareAndSet. One is reference and the other is timestamp. How does it implement atomicity? In fact, the internal AtomicStampedReference combines two values into an object and modifies a value. Let's look at the Code:

public boolean compareAndSet(V   expectedReference,                             V   newReference,                             int expectedStamp,                             int newStamp) {    Pair<V> current = pair;    return        expectedReference == current.reference &&        expectedStamp == current.stamp &&        ((newReference == current.reference &&          newStamp == current.stamp) ||         casPair(current, Pair.of(newReference, newStamp)));}

This Pair is an internal class of AtomicStampedReference. Its members include references and timestamps. The specific definition is as follows:

private static class Pair<T> {    final T reference;    final int stamp;    private Pair(T reference, int stamp) {        this.reference = reference;        this.stamp = stamp;    }    static <T> Pair<T> of(T reference, int stamp) {        return new Pair<T>(reference, stamp);    }}

AtomicStampedReference compares and modifies the combination of referenced values and timestamps to compare and modify a single value of this internal class Pair.

AtomicMarkableReference is another enhancement class of AtomicReference. Similar to AtomicStampedReference, it also associates a field with the reference, but this time it is a boolean flag, modify a flag only when the reference value and flag bit are the same.

Summary

This section describes the usage of various atomic variables and the principle CAS behind them. For the count and serial number generation requirements in the concurrent environment, consider using atomic variables instead of locks, CAS is the basis for Java concurrent packet sending. Based on it, it can implement efficient, optimistic, non-blocking data structures and algorithms. It is also the basis for lock, synchronization tools, and various containers in the concurrent packet.

In the next section, we will discuss and issue the explicit lock in the package.

(As in other sections, all the code in this section is in the https://github.com/swiftma/program-logic)

----------------

For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.

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.