lead

If you are still wondering what a thread is and what a process is, please Google it first, as these concepts are outside the scope of this article.

Using multithreading has only one purpose, which is to make better use of CPU resources, because all multithreaded code can be implemented using a single thread. This is only half true, because the code that reflects “multiple actors” should at least be given a thread for each character, otherwise it is impossible to simulate the real world, and certainly impossible to implement with a single thread: for example, the most common “producer, consumer model”.

Many people are confused about some of these concepts, such as synchronization, concurrency, etc. Let’s set up a data dictionary to avoid misunderstanding.

  • Multithreading: When the program (a process) runs with more than one thread
  • Parallelism and concurrency:
    • Parallelism: The simultaneous execution of a piece of processing logic by multiple CPU instances or by multiple machines is truly simultaneous.
    • Concurrency: Through the CPU scheduling algorithm, the user appears to be executing simultaneously, but is not really simultaneous from the CPU operation level. Concurrency often has a common resource in the scene, so there is a bottleneck for this common resource, we will use TPS or QPS to reflect the processing capacity of the system.



Concurrency and parallelism

  • Thread safety: Often used to describe a piece of code. In the case of concurrency, the code is used by multiple threads, the order in which the threads are scheduled does not affect any results. This time to use multithreading, we only need to pay attention to the system’s memory, CPU is not enough. Conversely, thread insecurity means that the order in which threads are scheduled affects the final result, such as a transaction-free transfer code:
    void transferMoney(User from, User to, float amount){
      to.setMoney(to.getBalance() + amount);
      from.setMoney(from.getBalance() - amount);
    }Copy the code
  • Synchronization: Synchronization in Java refers to the use of artificial control and scheduling to ensure that multi-threaded access to shared resources becomes thread-safe to ensure accurate results. Simply add the code above@synchronizedThe keyword. A good program is one that improves performance while ensuring accurate results. Thread safety takes precedence over performance.

All right, let’s get started. I’m going to summarize what’s involved with multithreading in several parts:

  1. Tie the horse: The state of the thread
  2. Inner workings: A method (mechanism) that every object has
  3. Taizu Changquan: Basic thread class
  4. Nine Yin true classics: advanced multithreaded control class

Tie the horse: The state of the thread

Here are two images:




Thread state




Thread state transition

The states are obvious, but it’s worth mentioning the “blocked” state: a thread can be blocked while Running

  1. The join() and sleep() methods are called, the sleep() time ends or is interrupted, the join() is interrupted, and the IO is returned to the Runnable state, waiting for the JVM to schedule it.
  2. Call wait() to make the thread wait blocked pool until notify()/notifyAll(). The thread is awakened and placed in the lock blocked pool. The synchronization lock is released to return the thread to a Runnable state.
  3. A thread in the Running state is locked in a blocked pool by Synchronized, and the Synchronized lock is released into a Runnable state.

In addition, threads in the runnable state are the threads that are being scheduled, and the order in which they are scheduled is not necessarily the same. The yield method in the Thread class can turn a running Thread into a runnable.

Inner workings: A method (mechanism) that every object has

Synchronized, wait, and notify are synchronization tools available to any object. Let’s get to know them first




monitor

They are manual thread scheduling tools applied to synchronization problems. To get to the bottom of it, start with the concept of Monitor, where every object in Java has a monitor that monitors the reentrant of concurrent code. The monitor does not work in non-multithreaded coding, and does not work in synchronized range.

Wait /notify must exist in a synchronized block. Also, all three keywords are for the same monitor (the monitor of an object). This means that after wait, other threads can enter the synchronized block to execute.

When a right to use the code does not hold a monitor (as shown in figure 5 in the state, that is, from the synchronized block) to wait or notify, throws Java. Lang. IllegalMonitorStateException. This includes calling wait/notify of another object in a synchronized block, because different objects have different monitors and can throw this exception as well.

More usage:

  • Synchronized used alone:
    • Code block: As follows, in a multithreaded environment, methods in a synchronized block get the monitor of the lock instance, and if the instance is the same, only one thread can execute the block
      public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ .. do something } } }Copy the code
    • Direct to methods: this is equivalent to using a lock in the above code to lock the monitor of class Thread1. Further, if you are modifying a static method, lock all instances of the class.
      public class Thread1 implements Runnable { public synchronized void run() { .. do something } }Copy the code
  • Synchronized, Wait, and Notify: typical scenario producer-consumer problems

    Public synchronized void produce() {if(this.product >= MAX_PRODUCT) {try {wait(); System.out.println(" product is full, please try again later "); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println(" this. Product + "); notifyAll(); Public synchronized void consume() {if(this. Product <= MIN_PRODUCT) {try {public synchronized void consume() {if(this. Product <= MIN_PRODUCT) {try { wait(); System.out.println(" out of stock, fetch later "); } catch (InterruptedException e) { e.printStackTrace(); } return; } system.out.println (" this. Product + "); this.product--; notifyAll(); // Notify the waiting producer that the product can be produced}Copy the code
    volatile

    Multithreaded memory models: Main memory, working memory, while processing data, the thread loads the value from main memory to the local stack, and then saves it back. Each operation on this variable fires a load and save.




volatile

Variables used for multiple threads that are not volatile or final have the potential to produce unpredictable results (another thread changes the value, but a thread later sees the original value). In fact, the same property of the same instance has only one copy of itself. But multiple threads cache values, and volatile essentially means not caching them. Using volatile in thread-safe situations compromises performance.

Taizu Changquan: Basic thread class

The basic Thread class refers to the Thread class, the Runnable interface, and the Callable interface. The Thread class implements the Runnable interface, and starts a Thread method:

MyThread my = new MyThread(); my.start();Copy the code

Thread related methods:

// The current thread can transfer CPU control, Thread.sleep() public static thread.sleep () public static thread.sleep () // Calling other.join() in a thread will wait for other to complete before continuing the thread. Public join() public interrupte()Copy the code

About interrupts: It does not interrupt a running thread as the stop method does. From time to time, the thread checks the interrupt flag bit to determine whether the thread should be interrupted (if the interrupt flag value is true). Terminals affect only wait, sleep, and Join states. The interrupted thread throws InterruptedException. Thread.interrupted() checks whether the current Thread has been interrupted, returning Boolean synchronized cannot be interrupted while obtaining a lock.

Interrupt is a state! The interrupt() method simply sets this state to true. So a properly running program will not terminate without detecting state, whereas blocking methods such as WAIT will check and throw exceptions. If you add while(! Thread.interrupted()) can also leave the code body after interruption

Thread best practice: When writing, it is best to set the Thread name thread. name and set the Thread group ThreadGroup for easy management. In the event of a problem, the print thread stack (Jstack-PID) can see at a glance which thread is causing the problem and what the thread is doing.

How do I get exceptions in a thread




You can’t use a try or catch to get an exception in a thread

Runnable

Similar to the Thread

Callable

Future mode: a type of concurrent mode, which can have two forms, namely, non-blocking and blocking, isDone and GET respectively. The Future object is used to store the return value and state of the thread

ExecutorService e = Executors.newFixedThreadPool(3); The submit method has multiple parameter versions and supports callable as well as runnable interface types. Future future = e.submit(new myCallable()); Future.isdone () //return true,false No blocking Future.get () //return returns a value and blocks until the thread finishes runningCopy the code

Nine Yin true classics: advanced multithreaded control class

Java1.5 provides a very efficient and practical multithreaded package :java.util.concurrent, which provides a number of advanced tools to help developers write efficient, easy to maintain, structured Java multithreaded programs.

1. Ref;

Use: Save thread independent variables. When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each Thread that uses the variable, so each Thread can independently change its own copy without affecting the corresponding copy of other threads. It is used for user login control, such as recording session information.

Implementation: Each Thread holds a variable of type TreadLocalMap. This class is a lightweight Map that provides the same functions as a Map, except that buckets contain entries instead of a linked list of entries. Function is still a map. Take itself as the key and the target as value. The main methods are get() and set(T a). After set, a threadLocal -> a is maintained in the map, and a is returned when GET. ThreadLocal is a special container.

2. Atomic classes (AtomicInteger, AtomicBoolean…)

Using an Atomic Wrapper class such as atomicInteger, or using its own guarantee atom operations, is equivalent to synchronized

/ / the return value to a Boolean AtomicInteger.com pareAndSet (int expect, int the update)Copy the code

This method can be used to implement optimistic locking, considering the scenario originally mentioned in this article: A pays B $10, A deducts $10, and B adds $10. At this time, C gives B2 yuan, but B’s plus ten yuan code is about:

if(b.value.compareAndSet(old, value)){
   return ;
}else{
   //try again
   // if that fails, rollback and log
}Copy the code

AtomicReference For AtomicReference, maybe the object will lose its property, so oldObject == current, but oldobject.getPropertya! = current getPropertyA. That’s where your AtomicStampedReference comes in. This is also a very common idea, which is to add the version number

3. The Lock classes

Lock: in the java.util.concurrent package. There are three implementations:

  • ReentrantLock
  • ReentrantReadWriteLock.ReadLock
  • ReentrantReadWriteLock.WriteLock

The main purpose is the same as synchronized, both are to solve the synchronization problem, deal with the resource dispute and produced technology. The function is similar but there are some differences.

The differences are as follows:

  1. Lock is more flexible, you can freely define the sequence of unlocking multiple locks (synchronized is added first and solved later)
  2. Offers a variety of locking schemes, including Lock blocking, trylock non-blocking, lockInterruptible, and trylock with timeout.
  3. Essentially the same as synchronized
  4. The greater the ability, the greater the responsibility, must control the lock and unlock, otherwise it will lead to disaster.
  5. And Condition class.
  6. Higher performance, for example:



Performance comparison between synchronized and Lock

The ReentrantLock is reentrantable in that the thread holding the lock can hold it for an equal number of times before the lock is actually released. The method of use is:

1. Create an instance

static ReentrantLock r=new ReentrantLock();Copy the code

2. Lock

R.l ock () or r.l ockInterruptibly ();Copy the code

Here, too, the latter can be interrupted. After thread A locks, thread B (lockInterruptibly) will stop blocking, give up fighting for resources, and enter the catch block after calling B.interrupt (). (If you use the latter, you must throw Interruptable Exception or catch)

3. Release the lock

r.unlock()Copy the code

Must do! What must be done? Put it in finally. To prevent abnormal out of the normal process, resulting in disaster. As an added tidbit, finally can be trusted: after testing, statement execution ina finally block can be guaranteed even when an OutofMemoryError occurs.

ReentrantReadWriteLock

Reentrant read-write lock (an implementation of read-write lock)

ReentrantReadWriteLock Lock = new ReentrantReadWriteLock() ReadLock r = lock. ReadLock (); WriteLock w = lock.writeLock();Copy the code

They both have lock,unlock methods. Write, write read mutually exclusive; Read not mutually exclusive. Efficient thread-safe code that can implement concurrent reads

4. The container classes

Here are two common ones:

  • BlockingQueue
  • ConcurrentHashMap

BlockingQueue blocks the queue. This class is an important class under the java.util.Concurrent package. As you can see from the Queue, this Queue is a one-way Queue that can add elements at the head of the Queue and remove or retrieve elements at the end. Similar to a pipeline, it is particularly suitable for some applications of fifO. The common queue interface mainly implements PriorityQueue (PriorityQueue), can be interested in research

BlockingQueue adds multi-threaded collaboration to queues:




BlockingQueue

In addition to the traditional queue functionality (the two columns on the left of the table), there are also blocking interfaces put and Take, and blocking interfaces offer and poll with timeout functionality. Put blocks when the queue is full until it is woken up when there is space; Take blocks when the queue is empty and is not woken up until something is taken. It is especially useful for producer-consumer models and is a miracle.

Common blocking queues are:

  • ArrayListBlockingQueue
  • LinkedListBlockingQueue
  • DelayQueue
  • SynchronousQueue

ConcurrentHashMap Efficient thread-safe hash map. Compare hashTable, concurrentHashMap, and HashMap

5. Management class

The management class concept is more general and is used to manage threads. It is not multithreaded per se, but provides mechanisms to do some encapsulation using the tools described above. Noteworthy management lessons learned: If you don’t know about this class, you should know about the previously mentioned ExecutorService, which makes it very convenient to open your own thread pool:

ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e = Executors.newSingleThreadExecutor(); ExecutorService e = Executors.newFixedThreadPool(3); // The first type is variable size thread pool, which allocates threads according to the number of tasks, the second type is single thread pool, which is equivalent to FixedThreadPool(1) // the third type is fixed size thread pool. // Then run e.execute(new MyRunnableImpl());Copy the code

This class is internally implemented through ThreadPoolExecutor, and it helps to understand thread pool management. They are essentially implementations of the ThreadPoolExecutor class. See Javadoc:




Parameter Description ThreadPoolExecutor

CorePoolSize: the initial and minimum number of threads in the pool, which is maintained even in idle state. MaximumPoolSize: The maximum number of threads that never grow beyond this value. KeepAliveTime: When the number of threads in the pool is higher than corePoolSize, how much time passes before the excess idle threads are collected. In wait state unit: TimeUnit. You can use an instance of TimeUnit, for example, timeunit.milliseconds workQueue: indicates the waiting place for a Runnable task. This parameter affects scheduling policies, such as whether it is fair or not. Starving threadFactory: a threadFactory class with default implementation, which implements the threadFactory interface and passes it in as a parameter if needed.

Please note that this class is very common and accounts for 80% of the author’s multithreading problems.