Advanced Java (1) -- Concurrency

Java Platform, Standard Edition (Java SE) 11
Official website DEV

JDK 11 Documentation

Java Basics - Concurrency

Concurrency

Java® Language Specification (Java SE 11 Edition)

The Java Tutorial is written for JDK 8. The examples and practices described on this page do not take advantage of improvements introduced in later releases, and may use techniques that are no longer available.

Computer users take for granted that their systems can do more than one thing at a time. They figured they could keep working in their word processor while other apps downloaded files, managed print queues, and streamed audio. Even a single application is often expected to do more than one thing at a time. For example, a streaming audio application must simultaneously read digital audio from the network, decompress it, manage playback, and update its display. Even a word processor should always be ready to respond to keyboard and mouse events, no matter how busy it is, to reformat text or update the display. Software that can do these things is called concurrent software ( concurrent software).

The Java platform was designed from the ground up to support concurrent programming, with basic concurrency support in the Java programming language and Java class libraries. Beginning with version 5.0, the Java platform also includes advanced concurrency APIs. This lesson introduces the platform's basic concurrency support and summarizes java.util.concurrentsome of the high-level APIs in the package.

1. Process and thread

In concurrent programming, there are two basic execution units: process ( processes) and thread ( threads). In the Java programming language, concurrent programming is mostly concerned with threads . However, progress is also important.

A computer system typically has many active processes and threads. This is true even in systems with only one execution core, so only one thread is actually executing at any given moment. time slicingThe processing time of a single core is shared between processes and threads through an operating system feature called time slicing ( ).

It is becoming more common for computer systems to have multiple processors or processors with multiple execution cores. This greatly enhances the system's ability to execute processes and threads concurrently -- but concurrency is possible even on simple systems without multiple processors or execution cores.

1.1 Process

A process has a self-contained execution environment. A process usually has a complete, private set of basic runtime resources; in particular, each process has its own memory space.

进程Often regarded as a synonym for 程序or应用程序 . However, what a user sees as a single application may actually be a set of cooperating processes . To facilitate inter-process communication, most operating systems support inter-process communication (Inter Process Communication, IPC) resources, such as pipes ( pipe) and sockets ( socket). IPC is used not only for communication between processes on the same system, but also between processes on different systems.

Most implementations of the Java virtual machine run as a single process . A Java application can create additional processes using the ProcessBuilder object. Multi-process applications are beyond the scope of this lesson.

1.2 Threads

Threads are sometimes called lightweight processes ( lightweight processes) . Both processes and threads provide an execution environment, but creating a new thread requires fewer resources than creating a new process .

Threads exist within processes - each process has at least one thread . Threads share the resources of the process, including memory and open files. This makes communication more efficient, but also potentially problematic.

Multithreaded execution is a fundamental feature of the Java platform . Every application has at least one thread -- at least a few, if you count "system" threads that perform tasks like memory management and signal handling. But from an application programmer's point of view, you start with only one thread, called the main thread ( main thread) . This thread is able to create other threads, which we will demonstrate in the next section.

2. Thread object

Each thread is associated with an instance of the Thread class. ThreadThere are two basic strategies for creating concurrent applications using objects.

  • To directly control thread creation and management, simply instantiate a each time your application needs to start an asynchronous task Thread.
  • To abstract thread management from the rest of the application, pass the application's tasks to an Executor( executor).

This section documents Threadthe use of the objects. Executors ( executor) are discussed along with other higher-order concurrency objects .

2.1 Define and start a thread

The application that creates Threadthe instance must provide code that will run in that thread. There are two methods:

  • Provide an Runnableobject. The Runnable interface defines a method runused to contain the code executed in the thread. RunnableThe object is passed to Threadthe constructor, as in the HelloRunnable example:
public class HelloRunnable implements Runnable {
    
    

    public void run() {
    
    
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
    
    
        (new Thread(new HelloRunnable())).start();
    }

}
  • subclass Thread. ThreadThe class itself implements Runnableeven though its runmethods do nothing. Applications can inherit Threadand provide their own runimplementation, such as the HelloThread example:
public class HelloThread extends Thread {
    
    

    public void run() {
    
    
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
    
    
        (new HelloThread()).start();
    }

}

Note that both examples call Thread.start to start a new thread.

Which style should you use? The first idiom uses Runnableobjects, which are more general because Runnableobjects can inherit Threadfrom other classes . The second idiom is easier to use in simple applications, but it is Threadlimited by the fact that the task class must be a descendant of . This lesson focuses on the first approach, which separates Runnablethe task from the Threadobjects that perform it. This method is not only more flexible, but also suitable for the high-level thread management API described later.

ThreadThe class defines a number of methods useful for thread management. These methods include static methods that provide information about or affect the state of the thread calling the method. Other methods are called by the management thread and Threadother threads involved with the object. We'll examine some of these methods in the following sections.

2.2 Use Sleep to suspend execution

Thread.sleepCauses the current thread to suspend execution for a specified period of time . This is an efficient way to give processor time to other threads of an application or other applications that may be running on the computer system . sleepmethod can also be used to adjust the speed (as shown in the example below), and to wait for another thread that needs time for a task (as shown in the example in a later subsection SimpleThreads).

Two overloaded versions are provided sleep: one specifying the sleep time in milliseconds and the other specifying the sleep time in nanoseconds. However, these sleep times are not guaranteed to be precise , as they are limited by the capabilities provided by the underlying operating system. Additionally, sleep cycles can interruptsbe terminated via interrupt() , which we'll see in a later section. Under no circumstances can it be assumed that the call sleepwill suspend the thread for the specified period of time.

The SleepMessages example uses to sleepprint messages every 4 seconds:

public class SleepMessages {
    
    
    public static void main(String args[])
        throws InterruptedException {
    
    
        String importantInfo[] = {
    
    
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };

        for (int i = 0;
             i < importantInfo.length;
             i++) {
    
    
            //Pause for 4 seconds
            Thread.sleep(4000);
            //Print a message
            System.out.println(importantInfo[i]);
        }
    }
}

Note mainthe statement that it throws InterruptedException. This is the exception thrown when another thread sleepinterrupts the current thread while it is active . sleepSince this application does not define another thread that caused the interrupt, it does not need to catch InterruptedException.

2.3 Interrupts

An interrupt is an indication to a thread that it should stop what it is doing and do something else . It's up to the programmer to decide how exactly a thread responds to interrupts, but thread termination is very common. This is the usage emphasized in this lesson.

A thread sends an interrupt by calling interrupt on the interrupted thread Threadobject . For the interrupt mechanism to work properly, the interrupted thread must support its own interrupts.

support interrupt

线程如何支持自己的中断?It depends on what it's currently doing . If the thread calls the throwing method frequently InterruptedException, it only needs to return from the run method after catching the exception . For example, suppose SleepMessagesthe central message loop in the example is in a method Runnableof the thread's object run. It can then be modified like this to support interrupts:

for (int i = 0; i < importantInfo.length; i++) {
    
    
    // Pause for 4 seconds
    try {
    
    
        Thread.sleep(4000);
    } catch (InterruptedException e) {
    
    
        // We've been interrupted: no more messages.
        return;
    }
    // Print a message
    System.out.println(importantInfo[i]);
}

Many InterruptedExceptionmethods that throw (eg sleep) are designed to cancel their current operation and return immediately when an interrupt is received.

InterruptedExceptionWhat if the thread doesn't call the throwing method for a long timeThread.interrupted ? It then has to call it periodically and return if an interrupt is received true. For example:

for (int i = 0; i < inputs.length; i++) {
    
    
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
    
    
        // We've been interrupted: no more crunching.
        return;
    }
}

In this simple example, the code just tests for interrupts, and exits the thread when an interrupt is received. In more complex applications, InterruptedExceptionit might make more sense to throw:

if (Thread.interrupted()) {
    
    
    throw new InterruptedException();
}

This allows interrupt handling code to be concentrated in catchclauses.

interrupt status flag

The interrupt mechanism is interrupt statusimplemented using an internal flag called the interrupt status ( ). Call Thread.interruptto set this flag. When a thread checks for an interrupt by calling a static method Thread.interrupted, the interrupt status will be cleared . The non-static isInterruptedmethod is used by one thread to query the interrupt status of another thread, it does not change the interrupt status flag.

By convention, any InterruptedExceptionmethod that exits by throwing clears the interrupt status on exit. However, there is always the possibility that the interrupt status will interruptbe set again immediately by another calling thread.

2.4 Joins

joinmethod allows one thread to wait for the completion of another thread . If tit is an object that a thread is executing Thread,

t.join();

Causes the current thread to suspend execution until tthe thread terminates. joinThe overload of allows the programmer to specify a wait time . However, as with sleep, joinrelies on the timing of the operating system, so you should not assume that you joinwill wait exactly as you specify.

Similar to sleep, respond to interrupts joinby using InterruptedExceptionexit.

Example of SimpleThreads

The following example brings together some of the concepts from this section. SimpleThreads consists of two threads. The first is the main thread that every Java application has. The main thread creates a new thread from Runnablethe object, , and waits for it to complete. MessageLoopIf MessageLoopa thread takes too long to complete, the main thread interrupts it.

MessageLoopThe thread prints out a series of messages. If interrupted before printing all messages, MessageLoopthe thread will print a message and exit.

public class SimpleThreads {
    
    

    // Display a message, preceded by
    // the name of the current thread
    static void threadMessage(String message) {
    
    
        String threadName =
            Thread.currentThread().getName();
        System.out.format("%s: %s%n",
                          threadName,
                          message);
    }

    private static class MessageLoop
        implements Runnable {
    
    
        public void run() {
    
    
            String importantInfo[] = {
    
    
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };
            try {
    
    
                for (int i = 0;
                     i < importantInfo.length;
                     i++) {
    
    
                    // Pause for 4 seconds
                    Thread.sleep(4000);
                    // Print a message
                    threadMessage(importantInfo[i]);
                }
            } catch (InterruptedException e) {
    
    
                threadMessage("I wasn't done!");
            }
        }
    }

    public static void main(String args[])
        throws InterruptedException {
    
    

        // Delay, in milliseconds before
        // we interrupt MessageLoop
        // thread (default one hour).
        long patience = 1000 * 60 * 60;

        // If command line argument
        // present, gives patience
        // in seconds.
        if (args.length > 0) {
    
    
            try {
    
    
                patience = Long.parseLong(args[0]) * 1000;
            } catch (NumberFormatException e) {
    
    
                System.err.println("Argument must be an integer.");
                System.exit(1);
            }
        }

        threadMessage("Starting MessageLoop thread");
        long startTime = System.currentTimeMillis();
        Thread t = new Thread(new MessageLoop());
        t.start();

        threadMessage("Waiting for MessageLoop thread to finish");
        // loop until MessageLoop
        // thread exits
        while (t.isAlive()) {
    
    
            threadMessage("Still waiting...");
            // Wait maximum of 1 second
            // for MessageLoop thread
            // to finish.
            t.join(1000);
            if (((System.currentTimeMillis() - startTime) > patience)
                  && t.isAlive()) {
    
    
                threadMessage("Tired of waiting!");
                t.interrupt();
                // Shouldn't be long now
                // -- wait indefinitely
                t.join();
            }
        }
        threadMessage("Finally!");
    }
}

3. Synchronization

Threads communicate primarily by sharing access to fields and references to objects referenced by fields . This form of communication is very efficient, but two types of errors can occur: thread interference ( thread interference) and memory consistency errors ( memory consistency errors). The tool needed to prevent these errors is sync( synchronization).

However, synchronization may introduce thread contention, which occurs when two or more threads try to access the same resource at the same time, and causes the Java runtime to execute one or more threads slower, or even suspend their execution. Starvation and livelock are forms of thread contention. See the Lifecycle section for more information.

This section covers the following topics:

  • Thread Interference : Describes how errors can be introduced when multiple threads access shared data.
  • Memory consistency errors describe errors due to inconsistent views of shared memory.
  • Synchronized methods describe a simple idiom that is effective against thread interference and memory consistency errors.
  • Implicit Locks and Synchronization describes a more general style of synchronization and describes how to implement synchronization based on implicit locks.
  • Atomic access discusses the general idea of ​​operations that cannot be interfered with by other threads.

3.1 Thread Interference

Consider a simple class called Counter

class Counter {
    
    
    private int c = 0;

    public void increment() {
    
    
        c++;
    }

    public void decrement() {
    
    
        c--;
    }

    public int value() {
    
    
        return c;
    }
}

Counteris designed to incrementincrement c by 1 and decrement decrement c by 1 for each call. However, if an Counterobject is referenced by multiple threads, interference between threads may prevent this from happening as desired.

Interference occurs when two operations run in different threads but interleave on the same data . This means that the two operations consist of multiple steps and the sequences of steps overlap.

CounterIt seems impossible for operations on an instance to interleave, since cboth operations on are single simple statements. However, even simple statements can be translated into multiple steps by the virtual machine . We will not examine the specific steps taken by the virtual machine - c++;it is enough to know that a single expression can be broken down into three steps:

  • Retrieve the current value of c.
  • Increment the retrieved value by 1.
  • Store the incremented value back in c.
    Expressions c--;can be decomposed in the same way, except the second step is decremented instead of incremented.

Assume that while thread A is calling increment , thread B calls decrement. If cthe initial value of 0, their interleaving actions may follow the following order:

  • Thread A: Retrieve c.
  • Thread B: Retrieve c.
  • Thread A: Increment retrieved value; result is 1.
  • Thread B: Decrement retrieved value; result is -1.
  • Thread A: Store result in c; c is now 1.
  • Thread B: Store result in c; c is now -1.

The result of thread A is lost and overwritten by thread b. This particular interleaving is just one possibility. Under different circumstances, it may be that the result of thread B is lost, or there is no error at all. Because thread interference errors are unpredictable, they are difficult to detect and fix.

3.2 Memory Consistency Error

Memory consistency errorsA memory consistency error ( ) occurs when different threads have inconsistent views of what should be the same data . The causes of memory consistency errors are complex and beyond the scope of this tutorial. Fortunately, programmers don't need to understand these reasons in detail. All it takes is a strategy to avoid them.

The key to avoiding memory consistency errors is understanding happens-beforerelationships . 这种关系只是保证一个特定语句的内存写入对另一个特定语句可见. To understand this, consider the following example. Suppose a simple intfield is defined and initialized:

int counter = 0;

counter Fields are shared between two threads A and B. Suppose thread A increments counter:

counter++;

Then, shortly after, thread B prints out counter:

System.out.println(counter);

If these two statements are executed in the same thread, then it is safe to assume that the output value is " 1". However, if these two statements were executed in separate threads, the value of the output would likely be " 0", because there is no guarantee that thread A 's counterchanges to thread B will be visible to thread B -- unless the programmer intervenes between the two statements A happens-before relationship is established.

There are several ways to create "happens-before" relationships. One of them is synchronization , which we will see in the next few sections.

We've seen two actions that create "happens-before" relationships.

  • Thread.startEvery statement that has a happens-before relationship with that statement also has a happens-before relationship with every statement executed by the new thread when the statement is invoked . The effects of code that caused a new thread to be created are visible to the new thread.
  • When a thread terminates and causes another thread Thread.jointo return, then all statements executed by the terminating thread have a happens-before relationship with all statements after a successful connection. The thread executing the join can now see the effects of the code in the thread.

See the java.util.concurrent package's Summary page for a list of operations that create a happens-before relationship .

3.3 Synchronization method

The Java programming language provides two basic synchronization idioms: synchronized methods and synchronized statements . The more complex synchronization statements of the two are described in the next section. This section describes the synchronization methods.

To make a method synchronous, simply add the keyword to its declaration synchronized:

public class SynchronizedCounter {
    
    
    private int c = 0;

    public synchronized void increment() {
    
    
        c++;
    }

    public synchronized void decrement() {
    
    
        c--;
    }

    public synchronized int value() {
    
    
        return c;
    }
}

If countit is SynchronizedCounteran instance of , then making these methods synchronized has two effects:

  • First, it is impossible for two calls to a synchronized method on the same object to interleave . When one thread executes a synchronized method for an object, all other threads calling synchronized methods on the same object block (suspend execution) until the first thread finishes processing the object.
  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent calls to synchronized methods on the same object . 这保证了对象状态的改变对所有线程都是可见的.

Note that constructors cannot be synchronized - using keywords in constructors synchronizedwill result in a syntax error. Synchronous constructors don't make sense because only the thread that created the object can access it while the object is being constructed.

Warning: 在构造将在线程之间共享的对象时,要非常小心,以免对该对象的引用过早地“泄漏”. For example, suppose you wish to maintain a named class instancesthat Listcontains each instance of the class. You may want to add the following line of code to the constructor: However, other threads can access the object
instances.add(this);
until construction of the object is complete .instances

Synchronized methods support a simple strategy to prevent thread interference and memory consistency errors: If an object is visible to multiple threads, then all reads and writes to variables of that object are done via synchronization synchronized. ( One important exception: finalfields, which cannot be modified after object construction, can be safely read by unsynchronized methods once the object has been constructed. ) This strategy works, but there can be liveness issues, which we will discuss in this lesson See you later.

3.4 Internal locks and synchronization

Synchronization is built around internal entities called intrinsic locks or monitor locks. (API specifications often refer to this entity simply as a "monitor") Internal locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state, and establishing the happens-before relationship that is critical to visibility.

Every object has an internal lock associated with it . By convention, threads that require exclusive and consistent access to object fields must acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when they are done using them. A thread owns the lock between acquiring the lock and releasing it. As long as a thread owns an internal lock, no other thread can acquire the lock. It blocks while another thread tries to acquire the lock.

When a thread releases an internal lock, a happens-before relationship is established between that operation and any subsequent acquisition of that lock.

Locks in synchronized methods

When a thread calls a synchronized method, it automatically acquires an internal lock on the method object and releases the lock when the method returns. Lock release occurs even if the return was caused by an uncaught exception.

You might be wondering what happens when a static synchronized method is called, since static methods are associated with a class rather than an object. In this case, the thread acquires Classan intrinsic lock on the object associated with the class. Therefore, access to a static field of a class is controlled by a lock that is distinct from that of any instance of the class.

synchronous statement

Another way to create synchronous code is to use synchronized statements . Unlike synchronized methods, synchronized statements must specify an object that provides an internal lock:

public void addName(String name) {
    
    
    synchronized(this) {
    
    
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

In this case, addNamethe method needs to synchronize changes to lastNameand nameCount, but also needs to avoid calling other object methods synchronously. (Calling methods of other objects from synchronized code can create problems that are described in the livveness section.) Without a synchronized statement, there must be a separate non-synchronized method whose sole purpose is to call nameList.add.

Synchronized statements are also useful for increasing concurrency through fine-grained synchronization . For example, suppose MsLuncha class has two instance fields c1and c2, which are never used together. All updates to these fields must be synchronized, but there's no reason to prevent updates to c1 from being interleaved with updates to c2 -- doing so would create unnecessary blocking, reducing concurrency. Instead of using synchronized methods and thislocks associated with , we created two objects to provide locks individually.

public class MsLunch {
    
    
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
    
    
        synchronized(lock1) {
    
    
            c1++;
        }
    }

    public void inc2() {
    
    
        synchronized(lock2) {
    
    
            c2++;
        }
    }
}

Be very careful with this idiom. You have to be absolutely sure that cross accessing the affected fields is indeed safe.

Reentrant synchronization

Recall that a thread cannot acquire a lock owned by another thread. 但是线程可以获得它已经拥有的锁. Reentrant synchronization ( reentrant synchronization) is achieved by allowing a thread to acquire the same lock multiple times. This describes a situation where synchronized code directly or indirectly calls a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronous code would have to take many extra precautions to avoid threads blocking themselves.

3.5 Atomic access

In programming, atomic operations are operations that occur effectively simultaneously. An atomic operation doesn't stop in the middle: it either happens completely, or it doesn't happen at all . The side effects of an atomic operation are not visible until the operation completes.

We've already seen that incrementing expressions (such as c++) are not described as atomic operations. Even very simple expressions can define complex operations that can be decomposed into other operations. However, you can specify atomic operations:

  • For reference variables and most primitive variables (all types except longand doubletypes), reads and writes are atomic.
  • Reads and writes are atomic for all volatilevariables declared as (including longand ).double

Atomic operations cannot be interleaved, so they can be used without worrying about thread interference . However, this does not remove all need to synchronize atomic operations, as memory consistency errors can still occur . Using volatile variables reduces the risk of memory consistency errors because volatileany write to a variable establishes a happens-before relationship with subsequent reads of that variable. This means that volatilechanges to variables are always visible to other threads. Furthermore, this also means that when a thread reads a volatile variable, it sees not only volatile the latest change to that variable, but also the side effects of the code that caused the change.

Using simple atomic variable accesses is more efficient than accessing these variables through synchronized code, but requires more care from the programmer to avoid memory consistency errors. Whether the extra effort is worth it depends on the size and complexity of the application.

Some classes in the java.util.concurrent package provide atomic methods that do not depend on synchronization. We'll discuss them in the section on Higher-Order Concurrency Objects .

4. Viability

The ability of a concurrent application to execute in a timely manner is called its liveness. This section describes the most common survival problem, deadlock , and then briefly describes two other survival problems, starvation and livelock.

4.1 Deadlock

A deadlock describes a situation where two or more threads are permanently blocked, waiting for each other . Here is an example.

Alphonse and Gaston are friends, and they both believe in good manners. A strict rule of good manners is that when you bow to a friend, you must keep bowing until your friend has a chance to return the bow. Unfortunately, this rule does not take into account the possibility of two friends bowing to each other at the same time. This sample application Deadlock simulates this possibility:

public class Deadlock {
    
    
    static class Friend {
    
    
        private final String name;
        public Friend(String name) {
    
    
            this.name = name;
        }
        public String getName() {
    
    
            return this.name;
        }
        public synchronized void bow(Friend bower) {
    
    
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
    
    
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
    
    
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
    
    
            public void run() {
    
     alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
    
    
            public void run() {
    
     gaston.bow(alphonse); }
        }).start();
    }
}

When Deadlockrunning, both threads will most likely block trying to call bowBack. Neither block ends because each thread is waiting for the other to exit.

4.2 Starvation and livelock

Starvation and livelocks are less common than deadlocks, but are still problems that every concurrent software designer can encounter.

hunger

Starvation ( Starvation) describes a situation where a thread cannot gain regular access to a shared resource and cannot make progress . This happens when a shared resource is held unavailable for a long time by a "greedy" thread. For example, suppose an object provides a synchronous method that usually takes a long time to return. If a thread calls this method frequently, other threads that also frequently synchronize access to the same object will usually be blocked.

livelock

A thread often responds to the actions of another thread. If another thread's operation is also a response to another thread's operation, then a livelock may result. As with a deadlock, a thread that is livelocked cannot perform further processing . However, the threads aren't blocked - they're just too busy responding to each other to get back to work. It's like two people overtaking each other in a hallway: Alphonse moves to his left to let Gaston pass, and Gaston moves to the right to let Alphonse pass. Seeing that they were still blocking each other, Alphone moved to his right, and Gaston moved to his left. They're still blocking each other, so...

5. Protection block

Threads often need to coordinate their actions. The most common form of coordination is the guarded block. Such a block first polls for a condition, which must be true before the block can continue. In order to do this properly, there are a few steps that need to be followed.
For example, suppose guardedJoyit is a method that must be performed after another thread sets the shared variable joy. In theory, such a method could simply loop until the condition is met, but such a loop is wasteful because it executes continuously while waiting.

public void guardedJoy() {
    
    
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {
    
    }
    System.out.println("Joy has been achieved!");
}

A more effective guard is to call Object.wait to suspend the current thread. waitThe call will not return until another thread notifies that some special event may have occurred - although not necessarily the event this thread is waiting for:

public synchronized void guardedJoy() {
    
    
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
    
    
        try {
    
    
            wait();
        } catch (InterruptedException e) {
    
    }
    }
    System.out.println("Joy and efficiency have been achieved!");
}

Note: Always called in a loop that tests the wait condition wait. Don't assume the interrupt is for the specific condition you're waiting on, or that the condition is still true.

Like many methods that suspend execution, waitcan throw InterruptedException. In this example, we can ignore the exception - we only care about joythe value.

Why is this version guardedJoysynchronous? Assume dis the object we used to call wait. An intrinsic lock that a thread d.waimust own when it calls t , otherwise an error is thrown. dCalling in a synchronized method waitis an easy way to acquire this internal lock .

When called wait, the thread releases the lock and suspends execution . At some time in the future, another thread will acquire the same lock and call Object.notifyAll , notifying all threads waiting on the lock that something important happened:

public synchronized notifyJoy() {
    
    
    joy = true;
    notifyAll();
}

Some time after the second thread releases the lock, the first thread reacquires the lock and waitcontinues execution by calling return.

Note: There is also a second notify method notify, which wakes up a single thread. Because notifyit doesn't allow you to specify the thread that gets woken up, it's only useful in massively parallel applications -- that is, programs with a large number of threads, all doing similar work. In such an application, you don't care which thread is woken up.

Let's use guard blocks to create a Producer-Consumer application. This type of application shares data between two threads: the producer ( producer) that creates the data, and the consumer ( consumer ) that processes it. The two threads communicate using a shared object. Coordination is essential: the consumer thread cannot attempt to retrieve data until the producer thread has delivered it, nor can the producer thread attempt to deliver new data if the consumer has not retrieved the old data.

In this example, the data is a series of text messages shared via objects of type Drop :

public class Drop {
    
    
    // Message sent from producer
    // to consumer.
    private String message;
    // True if consumer should wait
    // for producer to send message,
    // false if producer should wait for
    // consumer to retrieve message.
    private boolean empty = true;

    public synchronized String take() {
    
    
        // Wait until message is
        // available.
        while (empty) {
    
    
            try {
    
    
                wait();
            } catch (InterruptedException e) {
    
    }
        }
        // Toggle status.
        empty = true;
        // Notify producer that
        // status has changed.
        notifyAll();
        return message;
    }

    public synchronized void put(String message) {
    
    
        // Wait until message has
        // been retrieved.
        while (!empty) {
    
    
            try {
    
     
                wait();
            } catch (InterruptedException e) {
    
    }
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status
        // has changed.
        notifyAll();
    }
}

The producer thread defined in Producer sends a series of familiar messages. The string " DONE" indicates that all messages have been sent. To simulate the unpredictability of real applications, producer threads pause randomly between messages.

import java.util.Random;

public class Producer implements Runnable {
    
    
    private Drop drop;

    public Producer(Drop drop) {
    
    
        this.drop = drop;
    }

    public void run() {
    
    
        String importantInfo[] = {
    
    
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };
        Random random = new Random();

        for (int i = 0;
             i < importantInfo.length;
             i++) {
    
    
            drop.put(importantInfo[i]);
            try {
    
    
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {
    
    }
        }
        drop.put("DONE");
    }
}

The consumer thread defined in Consumer just retrieves the messages and prints them out until it retrieves the "DONE" string. This thread is also paused at random intervals.

import java.util.Random;

public class Consumer implements Runnable {
    
    
    private Drop drop;

    public Consumer(Drop drop) {
    
    
        this.drop = drop;
    }

    public void run() {
    
    
        Random random = new Random();
        for (String message = drop.take();
             ! message.equals("DONE");
             message = drop.take()) {
    
    
            System.out.format("MESSAGE RECEIVED: %s%n", message);
            try {
    
    
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {
    
    }
        }
    }
}

Finally, this is the main thread defined in ProducerConsumerExample , which starts the producer and consumer threads.

public class ProducerConsumerExample {
    
    
    public static void main(String[] args) {
    
    
        Drop drop = new Drop();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

Note: DropThe class was written to demonstrate protected blocks. To avoid reinventing the wheel, check the existing data structures in the Java Collections Framework before attempting to write your own data-sharing objects . See the Questions and Exercises section for more information.

6. Immutable objects

An object is considered immutable ( immutable) if its state cannot be changed after construction . Maximum reliance on immutable objects is widely accepted as a solid strategy for creating simple, reliable code.

Immutable objects are especially useful in concurrent applications. Because they cannot change state, they cannot be corrupted by thread interference and cannot be left in an inconsistent state.

Programmers are often reluctant to use immutable objects because they worry about the cost of creating new objects instead of updating objects in place. The impact of object creation is often overestimated, and is offset by some of the efficiencies associated with immutable objects. This includes reducing the overhead due to garbage collection, and eliminating the code needed to protect mutable objects from corruption.

The subsections below take a class whose instances are mutable, and derive a class from it that has immutable instances. In doing so, they give general rules for such conversions and demonstrate some of the advantages of immutable objects.

6.1 An example of a synchronization class

The SynchronizedRGB class defines objects that represent colors. Each object represents a color as three integers (representing the primary color value) and a string (representing the color name).

public class SynchronizedRGB {
    
    

    // Values must be between 0 and 255.
    private int red;
    private int green;
    private int blue;
    private String name;

    private void check(int red,
                       int green,
                       int blue) {
    
    
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
    
    
            throw new IllegalArgumentException();
        }
    }

    public SynchronizedRGB(int red,
                           int green,
                           int blue,
                           String name) {
    
    
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }

    public void set(int red,
                    int green,
                    int blue,
                    String name) {
    
    
        check(red, green, blue);
        synchronized (this) {
    
    
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.name = name;
        }
    }

    public synchronized int getRGB() {
    
    
        return ((red << 16) | (green << 8) | blue);
    }

    public synchronized String getName() {
    
    
        return name;
    }

    public synchronized void invert() {
    
    
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
        name = "Inverse of " + name;
    }
}

Must be used with care SynchronizedRGBto avoid being seen in an inconsistent state. For example, suppose a thread executes the following code:

SynchronizedRGB color =
    new SynchronizedRGB(0, 0, 0, "Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2

If another thread calls color.setafter statement 1, but before statement 2, myColorIntthe value of 2 will not match myColorNamethe value of . To avoid this outcome, the two statements must be bound together:

synchronized (color) {
    
    
    int myColorInt = color.getRGB();
    String myColorName = color.getName();
}

This inconsistency is only possible with mutable objects - SynchronizedRGBit wouldn't be a problem for the immutable version of

6.2 Strategies for defining immutable objects

The following rules define a simple strategy for creating immutable objects. Not all classes that are documented as "immutable" follow these rules . This doesn't necessarily mean that the creators of these classes were sloppy -- they probably had good reason to believe that instances of the class would never change after construction. However, this strategy requires complex analysis and is not suitable for beginners.

  1. Do not provide "setter" methods -- methods that modify the field or the object referenced by the field.
  2. Set all fields to finaland private.
  3. Do not allow subclasses to override methods. The easiest way is to declare the class as final. A more complicated approach would be to have a constructor private and construct the instance in a factory method.
  4. If instance fields contain references to mutable objects, these objects are not allowed to be changed:
    1) Do not provide methods to modify mutable objects.
    2) Don't share references to mutable objects. Never store a reference to an external mutable object passed to a constructor; if necessary, make a copy, and store a reference to the copy. Similarly, create copies of internal mutable objects when necessary to avoid returning the original object in methods.

Applying this policy SynchronizedRGBwill result in the following steps:

  1. There are two setter methods in this class. The first is setthat it transforms the object arbitrarily, which has no place in the immutable version of the class. The second is invertthat it can be adjusted by creating a new object rather than modifying an existing one.
  2. All fields are private; they are further decorated with final.
  3. The class itself is declared as final.
  4. There is only one field pointing to an object, and the object itself is immutable. Therefore, no modification of the state of the "contained" mutable object is required.

After these changes we have ImmutableRGB :

final public class ImmutableRGB {
    
    

    // Values must be between 0 and 255.
    final private int red;
    final private int green;
    final private int blue;
    final private String name;

    private void check(int red,
                       int green,
                       int blue) {
    
    
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
    
    
            throw new IllegalArgumentException();
        }
    }

    public ImmutableRGB(int red,
                        int green,
                        int blue,
                        String name) {
    
    
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }


    public int getRGB() {
    
    
        return ((red << 16) | (green << 8) | blue);
    }

    public String getName() {
    
    
        return name;
    }

    public ImmutableRGB invert() {
    
    
        return new ImmutableRGB(255 - red,
                       255 - green,
                       255 - blue,
                       "Inverse of " + name);
    }
}

7. Higher-order concurrent objects

7.1 Lock Objects

Synchronized code relies on a simple reentrant lock ( reentrant lock). This lock is convenient to use, but has many limitations. java.util.concurrent.locksPackage supports more complex locking idioms. We won't examine this package in detail, but will focus on its most basic interface, Lock .

LockObjects work much like the implicit locks used by synchronized code. As with implicit locks, only one thread can own an Lockobject at a time. Objects also support mechanisms Lockthrough their associated Conditionwait/notify objects .

LockThe biggest advantage objects have over implicit locks is their ability to exit attempts to acquire locks. tryLockThe method will exit if the lock is not immediately available or before the timeout expires (if specified) . If another thread sends an interrupt before acquiring the lock, lockInterruptiblythe method exits .

Let's use Lockobjects to solve the deadlock problem we saw in Liveness . Alphonse and Gaston train themselves to notice their friends bowing. We model this improvement by requiring Friendthe object to acquire locks for both participants before continuing execution . bowBelow is Safelockthe source code of the improved model. To demonstrate the versatility of this idiom, let's assume that Alphonse and Gaston are so enamored with their newfound ability to bow safely that they can't stop bowing to each other:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Random;

public class Safelock {
    
    
    static class Friend {
    
    
        private final String name;
        private final Lock lock = new ReentrantLock();

        public Friend(String name) {
    
    
            this.name = name;
        }

        public String getName() {
    
    
            return this.name;
        }

        public boolean impendingBow(Friend bower) {
    
    
            Boolean myLock = false;
            Boolean yourLock = false;
            try {
    
    
                myLock = lock.tryLock();
                yourLock = bower.lock.tryLock();
            } finally {
    
    
                if (! (myLock && yourLock)) {
    
    
                    if (myLock) {
    
    
                        lock.unlock();
                    }
                    if (yourLock) {
    
    
                        bower.lock.unlock();
                    }
                }
            }
            return myLock && yourLock;
        }
            
        public void bow(Friend bower) {
    
    
            if (impendingBow(bower)) {
    
    
                try {
    
    
                    System.out.format("%s: %s has"
                        + " bowed to me!%n", 
                        this.name, bower.getName());
                    bower.bowBack(this);
                } finally {
    
    
                    lock.unlock();
                    bower.lock.unlock();
                }
            } else {
    
    
                System.out.format("%s: %s started"
                    + " to bow to me, but saw that"
                    + " I was already bowing to"
                    + " him.%n",
                    this.name, bower.getName());
            }
        }

        public void bowBack(Friend bower) {
    
    
            System.out.format("%s: %s has" +
                " bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    static class BowLoop implements Runnable {
    
    
        private Friend bower;
        private Friend bowee;

        public BowLoop(Friend bower, Friend bowee) {
    
    
            this.bower = bower;
            this.bowee = bowee;
        }
    
        public void run() {
    
    
            Random random = new Random();
            for (;;) {
    
    
                try {
    
    
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
    
    }
                bowee.bow(bower);
            }
        }
    }

    public static void main(String[] args) {
    
    
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new BowLoop(alphonse, gaston)).start();
        new Thread(new BowLoop(gaston, alphonse)).start();
    }
}

Lock interface

// 如果锁可用,获取锁并立即返回值true。如果锁不可用,则此方法
// 将立即返回值false。
// 这个方法的典型用法参见文档
boolean tryLock();

7.2 Executors

In all of the previous examples, there is an affinity between Runnablethe task that the new thread (defined by its object) is performing and the thread itself (defined by the object). ThreadThis works well for small applications, but in larger applications it makes sense to separate thread management and creation from the rest of the application . Objects that encapsulate these functions are called executors. The following subsections describe the actuators in detail.

7.2.1 Actuator interface

java.util.concurrentThe package defines three actuator interfaces:

  • Executor: A simple interface that supports starting new tasks.
  • ExecutorService: Executora subinterface that adds features that help manage lifecycles, both for individual tasks and Executorthemselves.
  • ScheduledExecutorService: ExecutorServicesubinterface that supports future and/or periodic execution of tasks.

Executor interface

The Executor interface provides a method executedesigned as a drop-in replacement for the common thread creation idiom. If rit is an Runnable object, ebut an object of an Executorobject, you can e.execute(r);replace it with(new Thread(r)).start();

However, executethe definition of is less specific. Low-level usage creates a new thread and starts it immediately. Depending on Executorthe implementation, execute may do the same thing, but is more likely to use an existing worker thread to run r, or will be rplaced in a queue waiting for a worker thread to become available. (We'll describe worker threads in the thread pool section.)

java.util.concurrentThe executor implementations in are designed to take full advantage of the higher-level ExecutorServiceand ScheduledExecutorServiceinterfaces, although they also use the basic Executor ones.

ExecutorService interface

The ExecutorServicesubmit interface complements executethe method with a similar but more general method. As with execute, submitalso accepts Runnableobjects , but also accepts Callable objects, which allow a task to return a value. submitThe method returns a Future object, which is used to retrieve Callablethe return value and manage Callablethe Runnablestate of the task.

ExecutorServiceMethods are also provided for submitting large Callablecollections of objects. Finally, ExecutorServicea number of methods are provided to manage shutdown of executors. To support immediate shutdown, tasks should handle interrupts properly .

ScheduledExecutorService interface

The ScheduledExecutorService interface scheduleis used to supplement its parent ExecutorService's methods to execute or tasks scheduleafter a specified delayRunnableCallable . In addition, the interface defines scheduleAtFixedRateand scheduleWithFixedDelay, which repeatedly execute the specified task at defined intervals .

7.2.2 Thread pool

java.util.concurrentMost executor implementations in use a thread pool ( thread pools) consisting of worker threads ( worker threads). This type of thread exists independently of the task it executes, and is often used to perform multiple Runnabletasks .Callable

Using worker threads minimizes the overhead introduced by thread creation . Thread objects use a lot of memory, and in large-scale applications, allocating and deallocating many thread objects can incur significant memory management overhead.

A common type of thread pool is the fixed thread pool ( fixed thread pool). This type of pool always has a specified number of threads running; if a thread somehow terminates while it is still in use, it is automatically replaced with a new thread. Tasks are submitted to the pool via an internal queue that holds additional tasks whenever there are more active tasks than threads.

An important advantage of a fixed thread pool is that applications using it can degrade nicely ( degrade gracefully). To understand this, consider a web server application where each HTTP request is handled by a separate thread. If the application simply creates a new thread for each new HTTP request, and the system receives more requests than it can handle at once, the application will suddenly stop responding when the overhead of all those threads exceeds the capacity of the system All requests. Due to the limit on the number of threads that can be created, the application will not be able to serve HTTP requests as fast as they arrive, but it will serve them as fast as the system can handle.

An easy way to create an executor that uses a fixed thread pool is to call the newFixedThreadPool factory method in java.util.concurrent.Executors . This class also provides the following factory methods:

  • The newCachedThreadPool method creates an Executor with an expandable thread pool . This executor is suitable for applications that start many short-lived tasks.
  • The newsinglethreadeexecutor method creates an executor that executes tasks one at a time.
  • There are several factory methods that are versions of the above executors ScheduledExecutorService.

If none of the executors provided by the above factory methods meet your needs, then constructing an instance of java.util.concurrent.ThreadPoolExecutor or java.util.concurrent.ScheduledThreadPoolExecutor will give you additional options.

newFixedThreadPool

// 返回 新创建的线程池

public static ExecutorService newFixedThreadPool(int nThreads)

7.2.3 Fork/Join

fork/joinA frame is ExecutorServicean implementation of an interface that helps you take advantage of multiple processors . 它是为那些可以递归分解成小块的工作而设计的. The goal is to use all available processing power to enhance the performance of the application.

As with any ExecutorServiceimplementation, fork/jointhe framework distributes tasks to worker threads in a thread pool. fork/joinThe frame is different because it uses the task-stealing( work-stealing) algorithm. Worker threads with nothing to do can steal tasks from other threads that are still busy.

fork/joinThe center of the framework is the ForkJoinPool class, which is AbstractExecutorServicean extension of the class. ForkJoinPoolThe core task-stealing algorithm is implemented, and the ForkJoinTask process can be executed.

basic use

The first step in using fork/joina framework is to write code that performs some of the work. Your code should resemble the following pseudocode:

if (my portion of the work is small enough)
  do the work directly
else
  split my work into two pieces
  invoke the two pieces and wait for the results

Wrap this code in ForkJoinTaska subclass, usually using one of its more specialized types, RecursiveTask (which can return results) or RecursiveAction .

After ForkJoinTaskthe subclass is ready, create an object representing all the work to be done and pass it to ForkJoinPoolthe instance's invoke()methods.

blur for clarity

To help you understand fork/joinhow the framework works, consider the following example. Say you want to blur an image. The original source image is represented by an array of integers, where each integer contains the color value of a single pixel. The blurred destination image is also represented by an integer array of the same size as the source image.

Performing blurring is done by processing the source array one pixel at a time. Each pixel is averaged with its surrounding pixels (red, green, blue component averaged) and the result is placed in the destination array. Since the image is a large array, this process can take a long time. By implementing the algorithm using the fork/join framework, you can take advantage of concurrent processing on multiprocessor systems. Here's a possible implementation:

public class ForkBlur extends RecursiveAction {
    
    
    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;
  
    // Processing window size; should be odd.
    private int mBlurWidth = 15;
  
    public ForkBlur(int[] src, int start, int length, int[] dst) {
    
    
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    protected void computeDirectly() {
    
    
        int sidePixels = (mBlurWidth - 1) / 2;
        for (int index = mStart; index < mStart + mLength; index++) {
    
    
            // Calculate average.
            float rt = 0, gt = 0, bt = 0;
            for (int mi = -sidePixels; mi <= sidePixels; mi++) {
    
    
                int mindex = Math.min(Math.max(mi + index, 0),
                                    mSource.length - 1);
                int pixel = mSource[mindex];
                rt += (float)((pixel & 0x00ff0000) >> 16)
                      / mBlurWidth;
                gt += (float)((pixel & 0x0000ff00) >>  8)
                      / mBlurWidth;
                bt += (float)((pixel & 0x000000ff) >>  0)
                      / mBlurWidth;
            }
          
            // Reassemble destination pixel.
            int dpixel = (0xff000000     ) |
                   (((int)rt) << 16) |
                   (((int)gt) <<  8) |
                   (((int)bt) <<  0);
            mDestination[index] = dpixel;
        }
    }
  
  ...

Now implement the abstract compute()method that either performs the blurring directly or splits it into two smaller tasks. A simple array length threshold helps determine whether to perform work or split it.

protected static int sThreshold = 100000;

protected void compute() {
    
    
    if (mLength < sThreshold) {
    
    
        computeDirectly();
        return;
    }
    
    int split = mLength / 2;
    
    invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
              new ForkBlur(mSource, mStart + split, mLength - split,
                           mDestination));
}

If the previous method is in RecursiveActiona subclass of the class, then setting up the task ForkJoinPoolto run in is straightforward and involves the following steps:

  1. Create a task that represents all the work to be done.
// source image pixels are in src
// destination image pixels are in dst
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
  1. Create the one that will run the task ForkJoinPool.
ForkJoinPool pool = new ForkJoinPool();
  1. run task
pool.invoke(fb);

For the complete source code, including some extra code to create the target image file, see the ForkBlur sample.

standard implementation

In addition to using fork/joinframeworks to implement custom algorithms for tasks that execute concurrently on multiprocessor systems (as in the example in the previous section ForkBlur.java), there are some generally useful features in Java SE that have been implemented using fork/join frameworks. One such implementation was introduced in Java SE 8, used by the java.util.Arrays class for its parallelSort()methods. These methods are similar , but leverage concurrency sort()through the framework. fork/joinParallel sorting of large arrays is faster than sequential sorting when running on multiprocessor systems. However, exactly how these methods utilize fork/jointhe framework is beyond the scope of the Java Tutorial. See the Java API documentation for these information.

fork/joinAnother implementation of the framework is used by methods in the java.util.streams package, which is part of Project Lambda , scheduled for release in Java SE 8 . See the Lambda expressions section for more information.

7.3 Concurrent collections

java.util.concurrentPackage contains many additions to the Java Collections Framework. They are most easily categorized via the provided collection interface:

  • BlockingQueue defines a first-in-first-out data structure that blocks or times out when you try to add data to a full queue or retrieve data from an empty queue.
  • ConcurrentMap is a subinterface of java.util.Map that defines useful atomic operations. These operations delete or replace key-value pairs only if the key exists, or add key-value pairs only if the key does not exist . Making these operations atomic helps avoid synchronization. ConcurrentMapThe standard generic implementation of is ConcurrentHashMap , which is a concurrent analog of HashMap .
  • ConcurrentNavigableMap is ConcurrentMapa subinterface of ConcurrentNavigableMap that supports approximate matching . ConcurrentNavigableMapThe standard generic implementation of 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 operations that add an object to the collection and subsequent operations that access or delete that object .

7.4 Atomic variables

The java.util.concurrent.atomic package defines classes that support atomic operations on a single variable . All classes have getand setmethods, like reading and volatilewriting variables. That is, there is a happens-before relationship setwith any successor on the same variable . getAtomic compareAndSet methods also have these memory consistency properties, as do simple atomic arithmetic methods applied to integer atomic variables.

To see how to use this package, let's go back to the Counter class that was originally used to demonstrate thread interference:

class Counter {
    
    
    private int c = 0;

    public void increment() {
    
    
        c++;
    }

    public void decrement() {
    
    
        c--;
    }

    public int value() {
    
    
        return c;
    }

}

One way to protect Counteragainst thread interference is to make its methods synchronized, like SynchronizedCounter :

class SynchronizedCounter {
    
    
    private int c = 0;

    public synchronized void increment() {
    
    
        c++;
    }

    public synchronized void decrement() {
    
    
        c--;
    }

    public synchronized int value() {
    
    
        return c;
    }

}

For this simple class, synchronization is an acceptable solution. But for more complex classes, we may want to avoid unnecessary synchronization impact on liveness. Replacing the int field with something like AtomicCounterAtomicInteger allows us to prevent thread interference without resorting to synchronization :

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
    
    
    private AtomicInteger c = new AtomicInteger(0);

    public void increment() {
    
    
        c.incrementAndGet();
    }

    public void decrement() {
    
    
        c.decrementAndGet();
    }

    public int value() {
    
    
        return c.get();
    }

}

AtomicInteger

// 获取当前值。
public final int get() 

// 设置为给定的值。
public final void set(int newValue)

// 原子地设置为给定值并返回旧值。
public final int getAndSet(int newValue)

// 如果当前值==期望值,则自动将值设置为给定的更新值。
// 如果成功,则为 true;False表示实际值不等于期望值。
public final boolean compareAndSet(int expect, int update)

// 将当前值自动加1,返回更新后的值
public final int incrementAndGet()

// 将当前值自动减1,返回:更新后的值
public final int decrementAndGet()

// 自动将给定值添加到当前值,返回:更新后的值
public final int addAndGet(int delta)

// 将当前值自动加1,返回前值
public final int getAndIncrement() 

// 将当前值自动减1。返回前值
public final int getAndDecrement()

// 自动将给定值添加到当前值, 返回前值
public final int getAndAdd(int delta)

7.5 Concurrent random numbers

In JDK 7, java.util.concurrent includes a convenience class ThreadLocalRandomForkJoinTasks for applications that expect to use random numbers from multiple threads or .

For concurrent access, using ThreadLocalRandominstead Math.random()can reduce contention and ultimately lead to better performance.

All you need to do is call ThreadLocalRandom.current()and then call one of its methods to retrieve a random number. Here's an example:

int r = ThreadLocalRandom.current() .nextInt(4, 77);

ThreadLocalRandom

// 返回一个介于指定的起点(origin, 包括)和指定的边界(bound, 不包括)之间的
// 伪随机int值。
public int nextInt(int origin, int bound)

// 返回介于0(包含)和指定的边界(不包含)之间的伪随机int值。
public int nextInt(int bound)

// 返回一个伪随机int值。
public int nextInt() 

further references

  • Concurrent Programming in Java: Design Principles and Patterns (Second Edition) ( Concurrent Programming in Java: Design Principles and Pattern (2nd Edition)), by Doug Lea. This is the comprehensive work of a leading expert and architect of the Java Platform Concurrency Framework.
  • Java Concurrency in Practice ( Java Concurrency in Practice ) by Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea. A practical guide designed for beginners.
  • Effective Java Programming Language Guide (Effective Java Programming Language Guide, Second Edition), by Joshua Bloch. Although this is a general programming guide, its chapter on threads covers basic "best practices" for concurrent programming.
  • Concurrency: State Models & Java Programs (2nd Edition), by Jeff Magee and Jeff Kramer. Introduces concurrent programming through a combination of modeling and practical examples.
  • Java ConcurrencyAnimation ( Java Concurrent Animated): Animation showing usage of concurrency features.

Guess you like

Origin blog.csdn.net/chinusyan/article/details/130601537