1. atomicity
The atomicity of an operation is an operation that cannot be split, consistent with the atomic concept of a transaction in the database, that is either not executed or fully completed. There are many seemingly atomic operations that do not actually have "atomicity", such as:
Read-Modify-write operation: A statement as simple as i++ is not actually an "atomic" operation, it actually consists of three steps:
Read the original value
modifying values
Write a new value
And the three steps can be divided between the execution.
2. Race conditions
In multi-threaded environment, it is a race condition to encounter improper execution timing, the race condition does not necessarily lead to error, and some improper execution timing is required. A common statement structure that causes race conditions is:
Read-Modify-write structure: The update variable becomes a new value, and the new value is dependent on the old value.
"Check after execution" structure: to validate a condition before performing an operation, for example: If-else statement.
3. Add lock
For operations that need to guarantee atomicity, we can use locks to implement mutually exclusive access between multiple threads. Java provides a built-in lock mechanism to easily lock an operation, and every object in Java has a built-in lock, and we can use the built-in lock mechanism with the SYNCHRONIZED keyword, which typically consists of two forms: synchronized block and synchronized method.
//synchronized Blocksynchronized(obj)//using the built-in lock for object obj{ // .......}//Synchronized Method Public synchronizedmethod () {// .......}//is equivalent to Publicmethod () {synchronized( This) { // ....... }}
Java's built-in locks are reentrant, that is, the unit holding the lock is a thread, not a call. Therefore, for a thread that already holds lock X, it succeeds to request lock X again, which is common in recursive invocations:
Public void method () { synchronized(obj) { //... .. Method (); }}
A common mistake is to assume that locks are required only when writing to shared variables, in fact, as long as the shared variable is accessed, both the read operation and the write operation need to be locked.
4. Visibility
In general, we need to implement only one thread to perform some mutually exclusive operations, and we may also need to make other threads aware of the changes that occur after performing this operation, which requires a synchronization mechanism to ensure memory visibility after the operation is performed.
Both the built-in and display locks in Java support synchronization mechanisms, and common notify methods and wait methods are used for display locks. Java also enables lightweight synchronization with the volatile keyword, which is often used to implement variable memory visibility.
Actions on volatile modified variables are not reordered, the values in the cache are not used when accessing the volatile variable, and the values that are most recently written are always obtained. It is important to note that unlike lock locking, the volatile keyword only ensures memory visibility, but does not guarantee atomicity. The following conditions are required to use volatile variables:
(1) Access to the variable can be non-exclusive
(2) Updating the value of the variable does not depend on the old value (e.g., volatile does not guarantee atomicity of the self-increment operation), or it can ensure that only one thread updates the value of the variable
Volatile variables are often used as state variables, for example, a status token commonly used to determine whether a loop should exit:
volatile Boolean true; Public void run () { while(running) { //... .. }}publicvoid Stop () { false;}
5. Release and Escape
Publishing an object is an object that can be used in code outside the current scope, such as passing a reference to the object to another method, or returning a reference to the object in the public method. This allows the object to be modified in other code. When an object is published when it should not be published, it is called escaping.
Common escapes include:
(1) return a private member (mutable) reference in the public method
class test{ private Date birthday; Public void Getbirthday () { return birthday; }}
The date class object is not an immutable object, so once you return a reference to the Date class object, you can modify its value in the external code to indicate that the class is not well encapsulated.
(2) Passing this reference to other places before the object construction is complete
classa{Private intnum; PublicA () {NewB This); //Publish this reference to a Class B object in num++; //The intention is to expect Num to become 1, and the result is actually 2 } Public voidaddnum () {num++; } }classb{ PublicB (A A) {a.addnum (); }}
The above error is easier to see, a more obscure error condition is to start a thread in the constructor:
class a{ public A () { new Task (this). Start ();
//..... }} class Implements runnable{ a ; Task (a A) { this. A = A; } Public void Run () { // operation a }}
This may cause the class A object to be modified in another thread if it has not yet been created.
6. Thread closure
With only single-wire range access to data, there is no need for synchronization and mutual exclusion, a technique known as thread closure. In swing, threading technology is used extensively, and in swing technology, the operation of interface components is performed in the EDT (Event Dispath thread) threads. If you need to update the swing interface components in other threads, Swing provides the Invokelater method and the Invokeandwait method to submit requests to update the swing interface components. Therefore, the main function of a program written using swing should look like this:
Public Static void Main (string[] args) { eventqueue.invokelater (new Runnable () { public void Run () { new JFrame (); Mainframe.setvisible (true);}} );}
Some basic concepts of Java concurrent programming