J.U.C is a shorthand for java package java.util.concurrent, which is abbreviated in Chinese and contracted. It is a new basic api for writing concurrency in jdk1.5. Java practitioners must be familiar. At the same time, in the era of traffic, and contracting has also become a part of the advanced development interview. This article mainly talks about what's behind J.U.C. and then explores and contracts the lower level codes under LockSupport and Unsafe, which may be the beginning of a series of blog posts.
JCP is the abbreviation of Java Community Process, which is a process for developing and revising Java technical specifications. At the same time, we mentioned that JCP generally refers to the organization that maintains and manages this set of processes. 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 put forward by Sun company on December 8, 1998. It aims to promote the development of Java through the power of Java community. So far, it has developed from version 1.0 to the latest version 2.11 issued on July 21, 2019. There are four main stages in the JCP process, namely startup, draft release, final version and maintenance. A new Java function will appear in the next version of JDK from the startup stage to the successful completion of the whole process.
JSR is a shorthand for Java Specification Requests, and it is a specification to propose draft in the start-up phase of service JCP. Anyone who registers as a member of JCP can submit JSR to JCP. For example, you don't think the String operation method in JDK is practical in guava. If you can pass the JCP audit, you can see it in the next version of JDK. We are familiar with the proposals such as JSR-107 of Java caching api, JSR-303 of Bean attribute verification, etc. of course, we also need to talk about Java and contract JSR-166.
- JCP official website: https://jcp.org
Doug Lea, Chinese name is Doug Lee. J.U.C. is a university teacher in the United States. He is a great God. Before JDK 1.5, we could only use synchronized for concurrent access of the control program. At that time, the performance of synchronized was not optimized, and the performance was not good. The control thread could only use the wait and notify methods of Object. At this time, Doug Lea submitted the JSR-166 proposal to JCP. Before submitting JSR-166, Doug Lea had used the code similar to the J.U.C package function for more than three years. These codes are the prototype of J.U.C. let's take a look at these codes with historical flavor, and at the same time, they can also trigger our thinking. If there is no such code in JDK, then make it by yourself!
- Address of contract issuing used by Mr. Doug Lea before JDK 1.5: http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
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 { //wait to thread if inuse UU is true while (inuse_) { wait(); } inuse_ = true; } catch (InterruptedException ex) { notify(); throw ex; } } } /** * Release lock and inform 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 AbstractQueuedSynchronizer abstract class
As we all know about J.U.C, AbstractQueuedSynchronizer is the essence of this package. This is what we call AQS when we talk and contract. This framework is designed to provide a general mechanism for Atomicity management of synchronization state, blocking and unblocking of threads, and queuing. Under the contract, ReentrantLock and CountDownLatch are all implemented based on AQS. Look at whether the above prototypes are all implemented Sync interfaces. Are they familiar with each other? The following Sync is the prototype of AbstractQueuedSynchronizer.
- AQS design paper: http://gee.cs.oswego.edu/dl/papers/aqs.pdf
- Chinese Translation: https://www.cnblogs.com/dennyzhangdd/p/7218510.html
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 specification:
The target of this JSR is similar to that of the JDK 1.2 collections package:
- 1. Standardize a simple and extensible framework, which organizes common utilities into a small enough package so that users can easily learn and maintain them 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 variable.
- Special locks, barriers, semaphores and conditional variables.
- Queues and related collections designed for multithreading.
- Thread pool and custom execution framework.
We'll also look at support in the core languages and libraries. Note that these are completely different from the transaction concurrency control framework used in J2EE. (however, they can be useful for those who create such frameworks.)
2. What is the target Java platform?
J2SE
3. What needs will the proposed specification address for the Java community?
The underlying thread primitives (such as synchronized blocks, Object.wait, and Object.notify) are not enough for many programming tasks. As a result, application programmers are often forced to implement their own higher-level concurrent tools. This led to a huge duplication of work. In addition, it is well known that these facilities are difficult to be correct or even optimized. Concurrency tools written by application programmers are often incorrect or inefficient. Providing a standard set of concurrent utilities simplifies the task of writing various multithreaded applications and generally improves the quality of the applications that use them.
4. Why does the existing specification not meet this demand?
At present, developers can only use the concurrency control structure provided by Java language itself. These levels are too low for some applications and incomplete for others
5. Please briefly introduce the basic technology or technology:
Most packages will be implemented on top of 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. Does the API specification have the recommended package name? (i.e. javapi.something, org.something, etc.)
In java.util.concurrent
7. Is the proposed specification dependent on the specific operating system, CPU or I/O device you know?
Only indirectly, because JVM s running on different platforms may be able to optimize certain constructs in different ways.
8. Is there any unsolvable security problem in the current security model?
No,
9. Is there any problem of internationalization or localization?
No,
10. Is there any existing specification that may be out of date, abandoned or need to be revised?
No,
11. Please describe the expected schedule for the development of this specification.
The goal is to include this specification in the JSR of J2SE 1.5 (Tiger).
12. Please describe the expected working mode of the expert group dedicated to the development of this specification.
E-mail, conference call and unusual meetings. We will also use or create an open mailing list for discussion by other interested people outside the expert group.
Decrypt LockSupport and UnsafeAs mentioned earlier, AQS is the essence of concurrent package. LockSupport and Unsafe are the soul of all functions of JSR-166. Looking at the code of the whole concurrent package, LockSupport and Unsafe are everywhere. LockSupport provides two key methods, park and unpark, for blocking and releasing threads. The functions can be compared with wait and notify of Object, but they are more flexible than these two APIs. The following is the implementation simplified by the blogger (not in JDK)
/** The basic thread blocking basic class used to create locks and other synchronization classes provides basic thread control functions. */ public class LockSupport { private LockSupport() {} /** Unblock park. If not, its next call to {@ code park} will not block. */ 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 the addition of ConcurrentLinkedQueue, we can realize the semantics of fair Lock in the basic Lock function.
/** * Implementation of fair lock for FIFO * @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); // Blocking when not the first in the queue or unable to acquire a lock 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's added to the mysterious Unsafe JSR166?
Carefully, you may find that LockSupport is finally implemented based on unsafe park and unpark. Unsafe existed before JDK 1.5. What's added after JSR 166? Let's see what unsafe is. JDK source code is described as follows: a set of methods used to perform low-level, unsafe operations. Although the class and all methods are public, the use of the class is limited because only trusted code can get an instance of the class. As its name is, it's not safe, so the source code will not be provided directly after JDK 1.8. Other codes in JDK can directly see. java files in IDE, while unsafe only has. Class compiled code. Because unsafe is a real black magic, it can directly operate system level resources, such as system memory, threads, etc. JDK does not directly expose unsafe api. If you directly obtain unsafe instance in your own application like JDK, for example:
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
The SecurityException("Unsafe") will be 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 you can't go further. The methods in Unsafe are all native marked local methods, which are not implemented, such as:
public native void unpark(Object var1); public native void park(boolean var1, long var2);
If it's under Windows, the final call is to use the final package compiled into. dll developed by C + +, so as long as you see the C + + related code, you will know what's going on.
- JDK source code download address: http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip
- JDK source warehouse: http://hg.openjdk.java.net/
First, navigate to Unsafe.cpp. The file location is openjdk\hotspot\src\share\vm\prims\Unsafe.cpp. You will find that there are comments related to JSR166, such as / / These are the methods prior to the JSR 166 changes in 1.6.0. According to these information, we know that JSR166 has added five new methods in Unsafe, namely compareAndSwapObject, compareAndSwapInt, compareAndSwapLong, park and unpark. This is the core of CAS atomic operation and thread control in concurrent packages, and most of the functions in the contract are based on them. Finally, let's look at the specific implementation of park and unpark. The C language learned in school is not much worse, but the following code is still very clear in semantics.
// 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 always benefited from the code of J.U.C, and there are a lot of articles on interpretation and analysis of the source code of J.U.C on the Internet, but few of them talk about the birth of J.U.C. at the same time, we have found a lot of things about value sharing, and the technical context of the whole J.U.C is very clear, so we have recorded them. Since then, the blogger in the technology industry has another idol, Doug Lea, who hopes to become your idol after reading this article.
About the author:
Chen Kailing joined Kaijing technology in May 2016. At present, he is the structure group manager and fire fighting team leader of Kaijing R & D center. Independent blog KL blog( http://www.kailing.pub Blogger.