What's behind J.U.C. concurrent packages

Posted by jmosterb on Fri, 06 Sep 2019 12:47:34 +0200

Preface

J.U.C is the abbreviation of java package java.util.concurrent, short for concurrent package in Chinese. It is a new basic api for writing concurrency related in jdk1.5. Java practitioners must be familiar with, at the same time, in the traffic age, concurrent packages have become a must-ask part of senior development interviews. This article mainly talks about what is behind J.U.C. and then combines LockSupport and Unsafe to explore and concurrently package the underlying code, which may be the beginning of a series of blog posts.

About JCP and JSR

JCP, short for Java Community Process, is a process for developing and revising Java technical specifications. At the same time, we mention that JCP generally refers to the organization for maintaining and managing the process. This organization is mainly composed of Java developers and authorized non-profit organizations, who are in charge of the development direction of Java. JCP was proposed by Sun on December 8, 1998. It aims to promote the development of Java through the strength of the Java community. Up to now, it has developed from version 1.0 to the latest version 2.11 issued on July 21, 2009. There are four main stages in the JCP process, which are start, release draft, final version and maintenance. A new Java function is proposed from start-up stage, and will appear in the next version of JDK after the complete process is smoothly carried out.

JSR is the abbreviation of Java Specification Requests. It is a draft specification for service JCP startup phase. After anyone registers as a member of JCP, he or she can submit JSR to JCP. For example, you think that the String operation method in JDK is not as practical as that in guava. You suggest a JSR enhancement method in String. As long as it can be audited by JCP, you can see it in the next version of JDK. The well-known proposals include JSR-107 for Java caching api, JSR-303 for Bean property checking, and of course, the Java concurrent package JSR-166 for this article.

Doug Lea and his JSR-166

Doug Lea, Chinese name Doug Lee. J. U. C. is an American university teacher and a great god. J. U. C. came from him. Before JDK 1.5, we could only use synchronized for concurrent access to synchronized code. At that time, the performance of synchronized was not optimized, the performance was not good, and the control threads could only use Object wait and notify methods. Doug Lea submitted JSR-166 proposal to JCP at this time. Before submitting JSR-166, Doug Lea had used code similar to J.U.C package function for more than three years. These codes are the prototype of J.U.C. Here's a brief look at these historical codes, but also can trigger some thinking, if not in JDK. Yes, then make it by yourself.

Prototype of Lock Interface

public class Mutex implements Sync  {
  /** The lock status **/
  protected boolean inuse_ = false;
  @Override
  public void acquire() throws InterruptedException {
    if (Thread.interrupted()) {
      throw new InterruptedException();
    }
    synchronized(this) {
      try {
        //If inuse_is true, wait for threads
        while (inuse_) {
          wait();
        }
        inuse_ = true;
      }
      catch (InterruptedException ex) {
        notify();
        throw ex;
      }
    }
  }
  /**
   * Release the lock and notify the thread to continue execution
   */
  @Override
  public synchronized void release()  {
    inuse_ = false;
    notify(); 
  }
  @Override
  public boolean attempt(long msecs) throws InterruptedException {
    if (Thread.interrupted()) {
      throw new InterruptedException();
    }
    synchronized(this) {
      if (!inuse_) {
        inuse_ = true;
        return true;
      }
      else if (msecs <= 0) {
        return false;
      } else {
        long waitTime = msecs;
        long start = System.currentTimeMillis();
        try {
          for (;;) {
            wait(waitTime);
            if (!inuse_) {
              inuse_ = true;
              return true;
            }
            else {
              waitTime = msecs - (System.currentTimeMillis() - start);
              if (waitTime <= 0) 
                return false;
            }
          }
        }
        catch (InterruptedException ex) {
          notify();
          throw ex;
        }
      }
    }  
  }
}

Prototype of CountDownLatch

public class CountDown implements Sync {
    protected final int initialCount_;
    protected int count_;
    /**
     * Create a new CountDown with given count value
     **/
    public CountDown(int count) {
        count_ = initialCount_ = count;
    }
    @Override
    public void acquire() throws InterruptedException {
        if (Thread.interrupted()) {
            throw new InterruptedException();
        }
        synchronized (this) {
            while (count_ > 0) {
                wait();
            }
        }
    }
    @Override
    public boolean attempt(long msecs) throws InterruptedException {
        if (Thread.interrupted()) {
            throw new InterruptedException();
        }
        synchronized (this) {
            if (count_ <= 0) {
                return true;
            } else if (msecs <= 0) {
                return false;
            } else {
                long waitTime = msecs;
                long start = System.currentTimeMillis();
                for (; ; ) {
                    wait(waitTime);
                    if (count_ <= 0) {
                        return true;
                    } else {
                        waitTime = msecs - (System.currentTimeMillis() - start);
                        if (waitTime <= 0) {
                            return false;
                        }
                    }
                }
            }
        }
    }
    /**
     * Decrement the count.
     * After the initialCount'th release, all current and future
     * acquires will pass
     **/
    @Override
    public synchronized void release() {
        if (--count_ == 0) {
            notifyAll();
        }
    }

    /**
     * Return the initial count value
     **/
    public int initialCount() {
        return initialCount_;
    }

    public synchronized int currentCount() {
        return count_;
    }
}

Prototype of AbstractQueued Synchronizer abstract class

As you know from J.U.C., AbstractQueued Synchronizer is the essence of this package. This is what we call AQS when we talk about concurrent packages. This framework is designed to provide a universal mechanism for atomic management of synchronous state, blocking and unblocking threads, and queuing. ReentrantLock and CountDownLatch under concurrent package are all implemented based on AQS. Look at the above prototype implementations are all implemented Sync interfaces. Is it familiar? The following Sync is the prototype of AbstractQueued Synchronizer?

public interface Sync {

  public void acquire() throws InterruptedException;

  public boolean attempt(long msecs) throws InterruptedException;

  public void release();

  /**  One second, in milliseconds; convenient as a time-out value **/
  public static final long ONE_SECOND = 1000;
  /**  One minute, in milliseconds; convenient as a time-out value **/
  public static final long ONE_MINUTE = 60 * ONE_SECOND;
  /**  One hour, in milliseconds; convenient as a time-out value **/
  public static final long ONE_HOUR = 60 * ONE_MINUTE;
  /**  One day, in milliseconds; convenient as a time-out value **/
  public static final long ONE_DAY = 24 * ONE_HOUR;
  /**  One week, in milliseconds; convenient as a time-out value **/
  public static final long ONE_WEEK = 7 * ONE_DAY;
  /**  One year in milliseconds; convenient as a time-out value  **/
  public static final long ONE_YEAR = (long)(365.2425 * ONE_DAY);
  /**  One century in milliseconds; convenient as a time-out value **/
  public static final long ONE_CENTURY = 100 * ONE_YEAR;
}

Details of JSR-166

1. Please describe the proposed norms:

The goal of this JSR is similar to that of the JDK 1.2 Collections package:

  • 1. Standardize a simple, extensible framework that organizes commonly used utilities into a sufficiently small package that users can easily learn and maintain by developers.
  • 2. Provide some high-quality implementations.

The package will contain interfaces and classes that are useful in a variety of programming styles and applications. These classes include:

  • Atomic variables.
  • Special locks, barriers, semaphores and conditional variables.
  • Queues and related collections designed for multithreaded use.
  • Thread pool and custom execution framework.

We will also study the support in core languages and libraries.
Note that these are completely different from the transaction concurrency control framework used in J2EE. (But for those who create such frameworks, they can be very useful.)

2. What is the target Java platform?

J2SE

3. What needs will the proposed specification address in the Java community?

The underlying thread primitives (such as synchronized blocks, Object.wait and Object.notify) are not sufficient for many programming tasks. As a result, application programmers are often forced to implement their own higher-level concurrency tools. This leads to huge duplication of work. In addition, it is well known that these facilities are difficult to be correct or even more difficult to optimize. Concurrent tools written by application programmers are usually incorrect or inefficient. Providing a standard set of concurrent utilities simplifies the task of writing various multithreaded applications and generally improves the quality of applications that use them.

4. Why are the existing norms not meeting this demand?

Currently, developers can only use concurrency control structures provided by the Java language itself. For some applications, these levels are too low, while for others they are incomplete.

5. Please briefly introduce the basic technology or technology:

Most software packages will be implemented on low-level Java constructs. However, there are some key JVM / language enhancements for Atomicity and monitors that are necessary to achieve efficient and correct semantics.

6. Do API specifications have recommended package names? (javapi.something, org.something, etc.)

In java.util.concurrent

7. Does the proposed specification depend on any specific operating system, CPU or I/O device you know?

Only indirectly, because JVM s running on different platforms may be able to optimize some constructs in different ways.

8. Is there an unsolvable security problem in the current security model?

No

9. Are there problems of internationalization or localization?

No

10. Are there any existing specifications that may become obsolete, obsolete or need to be revised?

No

11. Please describe the expected timetable for the formulation of this Code.

The goal is to include this specification in J2SE 1.5 (Tiger) JSR.

12. Please describe the expected mode of work of the expert group dedicated to the development of this norm.

E-mail, teleconferencing and unusual meetings. We will also use or create an open mailing list for discussion by interested people other than expert groups.

Decrypt LockSupport and Unsafe

AQS is the essence of concurrent package. LockSupport and Unsafe are the soul of all the functions of JSR-166 concurrent package. Looking at the code under concurrent package, LockSupport and Unsafe can be seen everywhere. LockSupport provides two key methods, park and unpark, to manipulate thread blocking and releasing. The functions can be analogous to Object wait and notify, but more flexible than the two api s. Here's a simplified blogger implementation (not in JDK)

/**
   The basic thread blocking base class for creating lock and other synchronization classes provides basic thread control functions.
 */
public class LockSupport {
    private LockSupport() {}
    /**
      Unblock the park, and if it's not blocked, its next call to {@code park} will ensure that it's not blocked
     */
    public static void unpark(Thread thread) {
        if (thread != null) {
            UNSAFE.unpark(thread);
        }
    }
    /**
      Block the current thread unless the unpark() method is called first.
     */
    public static void park() {
        UNSAFE.park(false, 0L);
    }

    //Hotspot implementation via intrinsics API
    private static final Unsafe UNSAFE;
    static {
        try {
            try {
                final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
                    @Override
                    public Unsafe run() throws Exception {
                        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                        theUnsafe.setAccessible(true);
                        return (Unsafe) theUnsafe.get(null);
                    }
                };
                UNSAFE = AccessController.doPrivileged(action);
            } catch (Exception e) {
                throw new RuntimeException("Unable to load unsafe", e);
            }
        } catch (Exception ex) { throw new Error(ex); }
    }
}

With park and unpark, we can also implement the function of Lock in this way. The code is as follows. With Concurrent LinkedQueue support, we can realize the semantics of fair lock on the basic function of lock.

/**
 * Implementation of Fair Lock for Simple First in First Out
 * @author: kl @kailing.pub
 * @date: 2019/9/2
 */
public class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        waiters.add(current);
        // Not the first or inaccessible lock blocking in the queue
        while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted()) {
                wasInterrupted = true;
            }
        }
        waiters.remove();
        if (wasInterrupted) {
            current.interrupt();
        }
    }
    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}

What does the mysterious Unsafe, JSR166 add?

Careful you may find that LockSupport is ultimately based on Unsafe parks and unpark s. Unsafe existed before JDK 1.5. What did it add after JSR 166? Let's first look at the origin of Unsafe. The JDK source code describes a set of methods for performing low-level, unsafe operations. Although this class and all methods are common, the use of this class is limited because only trusted code can obtain instances of this class. As its name is insecure, so no source code is provided directly after JDK 1.8. Other code in JDK can see. java files directly in IDE, while Unsafe only has. class compiled code. Because Unsafe really has black magic, it can directly operate system-level resources, such as system memory, threads and so on. JDK does not expose Unsafe's api directly to the outside world. If you get an instance of Unsafe directly in your own application like JDK, such as:

private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();

The exception SecurityException("Unsafe") is thrown directly. The correct way to get it is as follows:

    private static final Unsafe UNSAFE;
    static {
        try {
            try {
                final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
                    @Override
                    public Unsafe run() throws Exception {
                        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                        theUnsafe.setAccessible(true);
                        return (Unsafe) theUnsafe.get(null);
                    }
                };
                UNSAFE = AccessController.doPrivileged(action);
            } catch (Exception e) {
                throw new RuntimeException("Unable to load unsafe", e);
            }
        } catch (Exception ex) { throw new Error(ex); }
    }

In fact, when you go deep into Unsafe, you will find that the methods in Unsafe are all local methods of native markup, which are not implemented, such as:

    public native void unpark(Object var1);

    public native void park(boolean var1, long var2);

If it's in Windows, the final call is the package compiled into. dll using C++, so you can see the C++ related code and know what's going on.

First, locate Unsafe.cpp, the file location is: openjdkhotspotsrcsharevmprimsUnsafe.cpp, and you will find annotations related to JSR166, such as: // These are the methods prior to the JSR 166 changes in 1.6.0. According to this information, we know that JSR166 has added five new methods in Unsafe, namely compareAndSwapObject, compareAndSwapInt, compareAndSwapLong, park, unpark. This is the core of CAS atomic operation and thread control in concurrent package. Most of the functions in concurrent package are based on them. Finally, let's look at the concrete implementation of park and unpark. The C language learnt in school is much worse, but the following code is still semantically clear.

// JSR166
// -------------------------------------------------------

/*
 * The Windows implementation of Park is very straightforward: Basic
 * operations on Win32 Events turn out to have the right semantics to
 * use them directly. We opportunistically resuse the event inherited
 * from Monitor.
 *
void Parker::park(bool isAbsolute, jlong time) {
  guarantee (_ParkEvent != NULL, "invariant") ;
  // First, demultiplex/decode time arguments
  if (time < 0) { // don't wait
    return;
  }
  else if (time == 0 && !isAbsolute) {
    time = INFINITE;
  }
  else if  (isAbsolute) {
    time -= os::javaTimeMillis(); // convert to relative time
    if (time <= 0) // already elapsed
      return;
  }
  else { // relative
    time /= 1000000; // Must coarsen from nanos to millis
    if (time == 0)   // Wait for the minimal time unit if zero
      time = 1;
  }

  JavaThread* thread = (JavaThread*)(Thread::current());
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Don't wait if interrupted or already triggered
  if (Thread::is_interrupted(thread, false) ||
    WaitForSingleObject(_ParkEvent, 0) == WAIT_OBJECT_0) {
    ResetEvent(_ParkEvent);
    return;
  }
  else {
    ThreadBlockInVM tbivm(jt);
    OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
    jt->set_suspend_equivalent();

    WaitForSingleObject(_ParkEvent,  time);
    ResetEvent(_ParkEvent);

    // If externally suspended while waiting, re-suspend
    if (jt->handle_special_suspend_equivalent_condition()) {
      jt->java_suspend_self();
    }
  }
}

void Parker::unpark() {
  guarantee (_ParkEvent != NULL, "invariant") ;
  SetEvent(_ParkEvent);
}

epilogue

We have been benefiting from J.U.C's code, and there are many articles on the Internet to interpret and analyze J.U.C's source code. But there are few articles about the birth of J.U.C. What is behind J.U.C. When we understand and package the code in depth, we find a lot of value-sharing things. The whole technical context of J.U.C is also very clear, so we can remember them. It's recorded. Since then, the blogger in the technical community, and a worshipful idol Doug Lea, hope that after reading this article can also become your idol.

Topics: C++ Java JDK Programming jvm