The Java platfrom is designed from ground up to support concurrent. There are two units of excution: Processes and Threads. A system normally has multipul processes and threads, even if there is only single excution core, because processing time can be shared among threads and processes through time clicing.
Processes
A process generally has a complete, private set of basic run-time resources, for example each process has its own memory space. To facilitate comunication between processes, most systems support Inter Processes communication(IPC) resources, such as pips and sockets. IPC can be used not only between processes on the same system but also processes on different systems.
Most implements of Java virtual mechine run as single process. A Java application can create additional processes using a ProcessBuilder.
Threads
Thread can be called lightweight process, creating a new thread requires fewer resources than creating a process.
Threads exist within a process, every process has at least one thread. Threads share resources of the process it belongs to, including memory space and open files.
Every running application has at least sevral threads(system threads and main thread), system threads do thing like memory management and signal handling. From the view of programmer’s view, application start with just one thread(main thread), this thread has the ability to create additional threads.
Ways to use threads
- Directly create and manage thread, simply instantiate Thread each time the application needs to initiate an asynchronous task.
- Implement Runnable interface and pass Runnable object to Thread constructor
- Subclass Thread (This way is limited by the fact that the task class can not subclass a class other than Thread. So the way to employ Runnable object is more general)
- Pass application’s tasks to an excutor.
Methods of Thread Class
Sleep
Thread.sleep() cause the current thread to suspend excution for a specifited period. This is an efficitent means of making processor time available to the other threads of an application or other applications that might be running on the system. These sleep times are not guaranteed to be precise, because the are limit by the facilities provided by the underlying OS. The periods can be terminated by interrupts.
Interrupt
The interrupt mechanism is implemented using an internal flag known as interrupt status. Invokeing ThreadObject.interrupt sets this flag. When a thread checks for an interrupt by Thread.interrupted, interrupt status is cleared. The non-static isInterrupt method, which is used by other thread to query status will not clear flag. Any method exits by throwing InterruptedException clears interrupt status when it does so.
Join
The join method allows one thread to wait for the completion of another. t.join() cause the current thread to pause execution until t’s thread complete.
Synchronized
Constructors can not be synchronized, it is a syntax error and doesn’t make sense, because only the thread that creates an object should have the access to it while it is being constructed.
Intrinsic Locks and Reentrant Locks
Synchronized is built around an internal entity known as the intrinsic lock or monitor lock. Intrinsic lock paly a role in both aspects of synchronization: enforcing exclusive access to an object’s state and establishing happens-before relationship.
Every object has a intrinsic lock associated with it. A thread is said to own the object’s intrinsic lock between the time it has acquire the lock and release the lock. As long as the thread owns object’s lock no other thread can acquire the same lock, and will block until the lock has release. When a thread relase an intrinsic lock, a happends-before relationship is established between that action and subsequent acquisition of the same lock.
Synchronized methods
When a thread invoke a synchronized method, it automatically acquires the intrinsic lock of the method’s object and release it before it return. The lock release even if the return was caused by an uncaught exception.
As with static methods and fields, the thread acquires the intrinsic lock for the Class object associate with the class.
Synchronized statements
Synchronized statements must specify the object that provides the instrinsic lock.
Reentrant Synchcronized
Allowing a thread to acquire the same lock more than once enables Reentrant Synchronized. This describle a situation where synchronized code, directly or indirectly invokes a method that also contains synchronized code, and both sets of code use the same lock.
Atomic Access
In programming, an atomic action means that it either happens completely or it doesn’t happen at all. It cann’t be interrupt in the middle.
Some actions can be specify atomic:
- Reads and writes are atomic for reference variables and for most primitive variables(all type except long and double)
- Reads and writes are atomic for all variables declared volatile(including long and double)
Atomic actions cann’t be interleaved, but this does not eliminate all possible memory consistency errors.
Deadlock, Starvation and livelock
Deaklock describes a situation where two or more threads are blocked forever, waiting for each other.
Starvation describe a situation where a thread is unable to gain regular access to shared resources and is unable to make progress.
Thread communication
wait()
Object.wait() suspend the current thread, and wait() does not return until another thread has issued a notification that some special event have occured – though not necessarity the event this thread is waiting for.
When a thread invokes d.wait(), it must own the intrinsic lock for d. When wait() has invoked, the Thread release the lock and suspends execution. At some future time, another thread acquires the same lock and invokes Object.notifyAll(), infroming all threads waiting for that lock that something important has happened.
Always invoke wait inside a loop that tests for the condition being waiting for.
Producer-Consumer
1 | class Plate { |
Locks
The biggeast advantages of Lock objects over implicit locks is the ability to back out of an attempt to acquire a lock. The tryLock() method backs out if the lock is not availiable immediately or before a timeout expires.
Executors
Executors encapsulate threads creation and management, The java.util.concurrent package defines three executor interfaces:
- Executor, a simple interface that supports launching new tasks. The low-level idiom creates a new thread and launches it immediately. Depending on the Executor implementation, execute may do the same thing, but is more likely to use an existing worker thread to run r, or to place r in a queue to wait for a worker thread to become available.
- ExecutorService, a subinterface of Executor, which adds features that help manage the lifecycle, both of the individual tasks and of the executor itself. The ExecutorService interface supplements execute with a similar, but more versatile submit method. Like execute, submit accepts Runnable objects, but also accepts Callable objects, which allow the task to return a value. The submit method returns a Future object, which is used to retrieve the Callable return value and to manage the status of both Callable and Runnable tasks.
- ScheduledExecutorService, a subinterface of ExecutorService, supports future and/or periodic execution of tasks. The ScheduledExecutorService interface supplements the methods of its parent ExecutorService with schedule, which executes a Runnable or Callable task after a specified delay. In addition, the interface defines scheduleAtFixedRate and scheduleWithFixedDelay, which executes specified tasks repeatedly, at defined intervals.
Thread Pools
Most of the executor implementations in java.util.concurrent use thread pools, which consist of worker threads. This kind of thread exists separately from the Runnable and Callable tasks it executes and is often used to execute multiple tasks.
Using worker threads minimizes the overhead due to thread creation. Thread objects use a significant amount of memory, and in a large-scale application, allocating and deallocating many thread objects creates a significant memory management overhead.
One common type of thread pool is the fixed thread pool. This type of pool always has a specified number of threads running; if a thread is somehow terminated while it is still in use, it is automatically replaced with a new thread. Tasks are submitted to the pool via an internal queue, which holds extra tasks whenever there are more active tasks than threads.
Fork / Join
The fork/join framework is an implementation of the ExecutorService interface that helps you take advantage of multiple processors. It is designed for work that can be broken into smaller pieces recursively. The goal is to use all the available processing power to enhance the performance of your application.
As with any ExecutorService implementation, the fork/join framework distributes tasks to worker threads in a thread pool. The fork/join framework is distinct because it uses a work-stealing algorithm. Worker threads that run out of things to do can steal tasks from other threads that are still busy.
Concurrent Collections
The java.util.concurrent package includes a number of additions to the Java Collections Framework:
BlockingQueue defines a first-in-first-out data structure that blocks or times out when you attempt to add to a full queue, or retrieve from an empty queue.
ConcurrentMap is a subinterface of java.util.Map that defines useful atomic operations. These operations remove or replace a key-value pair only if the key is present, or add a key-value pair only if the key is absent. Making these operations atomic helps avoid synchronization. The standard general-purpose implementation of ConcurrentMap is ConcurrentHashMap, which is a concurrent analog of HashMap.
ConcurrentNavigableMap is a subinterface of ConcurrentMap that supports approximate matches. The standard general-purpose implementation of ConcurrentNavigableMap is ConcurrentSkipListMap, which is a concurrent analog of TreeMap.
All of these collections help avoid Memory Consistency Errors by defining a happens-before relationship between an operation that adds an object to the collection with subsequent operations that access or remove that object.
Atomic Variables
The java.util.concurrent.atomic package defines classes that support atomic operations on single variables. All classes have get and set methods that work like reads and writes on volatile variables. That is, a set has a happens-before relationship with any subsequent get on the same variable. The atomic compareAndSet method also has these memory consistency features, as do the simple atomic arithmetic methods that apply to integer atomic variables.
Concurrent Random Numbers
In JDK 7, java.util.concurrent includes a convenience class, ThreadLocalRandom, for applications that expect to use random numbers from multiple threads or ForkJoinTasks.