Getting started with multithreading, what is multithreading

Getting started with multithreading, what is multithreading

The main content comes from the multithreading chapter of "Java Core Technology Volume I". This part of the original book has nearly a hundred pages. It is recommended that energetic partners read the original book directly.

1. Multithreading concept


​ Multithreaded programs extend the concept of multitasking at a lower level: a program executes multiple tasks at the same time. Usually, each task is called aThread, A program that can run more than one thread at the same time becomesMultithreaded program. Compared with processes, threads are more "lightweight", and the overhead of creating and canceling a thread is much smaller than that of starting a new process.

​ In practical applications, multithreading is very useful. For example, a browser can download several pictures at the same time, and a web server needs to process several concurrent requests at the same time. In addition, the use of multiple threads can asynchronously perform some expensive operations, such as uploading large files, etc., to prevent the main thread from blocking.

2. Interrupt the thread


​ When the thread's run method executes the last statement in the method body and returns by executing the return statement, or when an exception that is not caught in the method occurs, the thread will terminate.

​ There is no way to force the thread to terminate. Use the interrupt method to request the termination of the thread. When the interrupt method is called on a thread, the interrupt status of the thread will be set. This is the boolean flag that every thread has. Each thread should check this flag from time to time to determine whether the thread is interrupted. (Blocked threads cannot detect interruption status)

Interrupting a thread just draws its attention, and the interrupted thread can decide how to respond to the interrupt.

3. Thread status


A thread can have the following 6 states:

  • New (Newly Created)
  • Runnable
  • Blocked
  • Waiting
  • Timed waiting
  • Terminated

The thread status is shown in the figure:
Insert picture description here

3.1 Newly created thread

​ When using the new operator to create a new thread, such as new Thread®, the thread has not yet started running. When a thread is in the newly created state, the program has not yet started to run the code in the thread. There is still some basic work to be done before the thread runs (the overhead of this step can be reduced through the thread pool).

3.2 Runnable threads

​ Once the start method is called, the thread is in the runnable state. ==A runnable thread may or may not be running, == It depends on how long the operating system provides for the thread to run. Once a thread starts running, it does not have to keep running all the time. In fact, the purpose of interrupting running threads is to allow other threads to get a chance to run. The preemptive scheduling system gives each runnable thread a time slice to execute the task. After the time slice of this thread is used up, the operating system deprives the thread of the right to run, and gives another thread a chance to run. The operating system will consider the priority of the thread to select the next thread.

3.3 Blocking threads and waiting threads

When a thread is in a blocked or waiting state, it does not run any code and consumes the least resources until the thread scheduler reactivates it.

  • When a thread tries to acquire an internal object lock, and the lock is held by other threads, the thread enters a blocked state. When all other threads release the lock, and the thread scheduler allows this thread to hold the lock, the thread will be programmed to a non-blocking state.
  • When a thread waits for another thread to notify the scheduler of a certain condition, this thread enters the waiting state.
  • Some methods will have a timeout parameter, and calling these methods will cause the thread to enter a timer waiting state. This state will remain until the timeout period expires or an appropriate notification is received.

3.4 Terminated thread

The thread will be terminated due to one of the following two reasons:

  • Naturally died because the run method exited normally.
  • It died unexpectedly because of an uncaught exception that terminated the run method.

You can call the stop method of the thread to kill a thread. This method throws a ThreadDeath error object, thereby killing the thread. However, the stop method is obsolete, don't call this method in your own code!

4. Thread attributes


The various attributes of threads include: thread priority, daemon thread, thread group, and processor that handles uncaught exceptions.

4.1 thread priority

​ In the Java programming language, every thread has a priority. By default, a thread will inherit the priority of its parent thread, you can use the setPriority method to increase or decrease the priority of any thread (the lowest is 1, the highest is 10).When building a program, the correctness of functions should not depend on priority.

One mistake should be avoided when using priority: If several high-priority threads do not enter an inactive state, the low-priority thread may never be able to execute. Whenever the scheduler decides to run a new thread, it will first choose among threads with high priority, which will starve the threads with low priority to death.

4.2 Daemon thread

​ The thread can be converted to a daemon thread by calling t.setDaemon(ture);. The sole purpose of a daemon thread is to provide services to other threads (timer threads are an example). When there are only daemon threads left, the virtual machine exits. therefore,The daemon thread should never access inherent resources, Such as files, databases, because it will be interrupted at any time or even in the middle of an operation.

void setDaemon(boolean isDaemon) method: Identifies the thread as a daemon thread or a user thread. This method must be called before the thread is started.

4.3 Uncaught exception handler

​ The thread's run method cannot throw any detected exceptions, but undetected exceptions will cause the thread to terminate. At this point, there is no need to use the catch clause to handle exceptions that can be propagated. The exception will be passed to a handler for uncaught exceptions before the thread dies.

​ The processor must belong to a class that implements the Thread.UncaughtExceptionHandler interface, which has only one method:

void uncaughtException(Thread t,Throwable e)

​ You can use the setUncaughtExceptionHandler method to install a processor for any thread, or you can use the static method setDefaultUncaughtExceptionHandler of the Thread class to install a default processor for all threads. If the default processor is not installed, the default processor is empty. If the processor is not installed for an independent thread, the thread processor is the ThreadGroup (thread group) object of the changed thread.

A thread group is a collection of threads that can be managed uniformly. By default, all threads created belong to the same thread group, but other groups may also be created. Now introduces better features for thread collection operations, therefore, it is recommended not to use thread groups in your own programs.

5. Synchronization


​ In most practical multi-threaded applications, two or more threads need to share access to the same data. If two threads access the same object, and each thread calls a method that modifies the state of the object, a corrupted object will be generated. This situation is commonly referred to asRace conditions

5.1 Lock object

​ There are two mechanisms to prevent code blocks from being interfered by concurrent access. The Java language provides a synchornized keyword to achieve this goal, and Java SE 5.0 introduces the ReentrantLock class.

5.2 Condition Object

​ Usually, a thread enters a critical section, but finds that it can only execute after a certain condition is met. Use a condition object to manage threads that have acquired a lock but cannot do useful work.

5.3 synchronized keyword

The key points of locks and conditions:

  • Locks can be used to protect code fragments. Only one thread can execute the protected code at any time.
  • The lock can manage threads trying to enter the protected code segment.
  • A lock can have one or more related condition objects.
  • Each condition object manages threads that have entered the protected code segment but cannot run yet.

There are some limitations to internal locks and conditions, including:

  • You cannot interrupt a thread that is trying to acquire a lock.
  • You cannot set a timeout when trying to acquire a lock.
  • Each lock has only a single condition, which may not seem enough

Suggestions for this part in actual coding:

  • It is best to neither use Lock/Condition nor the synchronized keyword(So ​​this paragraph skips some content of the synchronized keyword, the specific use is very simple, you can directly Baidu if you want to know). In many cases, the corresponding mechanism in the java.util.concurrent package can be used directly, and it will handle all the locking for us.
  • If the synchronized keyword is suitable for our program, it should be used as much as possible, so as to reduce the amount of code written and reduce the chance of error
  • Use them only when you specifically need the unique features provided by the Lock/Condition structure.

5.4 Synchronous blocking

	每一个Java对象有一个锁,线程可以通过调用同步方法获得锁,也可以通过进入一个同步阻塞获得锁。

​ Sometimes programmers use an object lock to implement additional atomic operations, which is actually calledClient lock

5.5 Monitor

​ Using monitors can ensure the safety of multithreading without the programmer needing to consider how to lock. The monitor has the following characteristics:

  • Monitors are classes that only contain private domains.
  • Each object of the monitor class has an associated lock.
  • Use this lock to lock all methods. In other words, if the client calls obj.method(), the lock of the obj object is automatically acquired at the beginning of the method call, and the lock is automatically released when the method returns. Because all domains are private, this arrangement can ensure that when a thread operates on the object, no other thread can access the domain.
  • The lock can have any number of related conditions.

If a method is declared with the synchronized keyword, then it behaves like a monitor method. The condition variable is accessed by calling wait/notifyAll/notify. However, Java objects are different from monitors in the following three aspects, which reduces thread safety:

  • Private when the domain is not required.
  • The method does not need to be synchronized.
  • Internal locks are available to customers.

5.6 Volatile domain

​ The volatile keyword provides a lock-free mechanism for synchronous access to the instance domain. If a domain is declared as volatile, then the compiler and the virtual machine know that the domain may be updated concurrently by another thread.

Volatile variables cannot provide atomicity. For example, the method:

public void flipDone(){done =!done;}

It is not guaranteed to flip the value in the field.

5.7 final variables

​ Unless a lock or volatile modifier is used, a field cannot be safely read from multiple threads. But when a shared domain is declared as final, you can safely access this domain. Such as:

final Map<String, Double>accounts = new HashMap<>();

​ Other threads will see this accounts variable after the constructor completes the construction. If final is not used, there is no guarantee that other threads will see the updated value of accounts when they see the ground. They may all see null instead of the newly constructed HashMap. Of course, == operations on this mapping table are not thread-safe. == If multiple threads are reading and writing this mapping table, they still need to be synchronized.

5.8 Atomicity

​ Assuming that no other operations are done except assignment to shared variables, these shared variables can be declared as volatile.

5.9 Deadlock

​ Locks and conditions cannot solve all the problems in multithreading. It is possible that all threads are blocked because each thread has to wait for more locks. Such a state is called a deadlock. There is nothing in the Java programming language that can avoid or break this kind of deadlock. The program must be carefully designed to ensure that no deadlock occurs.

5.10 Thread local variables

​ Sometimes you may want to avoid shared variables and use the ThreadLocal auxiliary class to provide individual instances for each thread. Suppose there is a static variable:

public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

​ If both threads do the following:

String dateStamp = dateFormat.format(new Date());

​ The internal data structure used by dateFormat may be destroyed by concurrent access. At this time, synchronization is very expensive, and it is wasteful to construct a local SimpleDateFormat object when needed. To construct an instance for each thread, you can use the following code :

public static final ThreadLocal<SimpleDateFormat>dateFormt = new ThreadLocal<SimpleDateFormat>(){
    
    
    protected SimpleDateFormat initialValue(){
    
    
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

​ To access specific formatting methods, you can call:

String dateStamp = dateFormat.get().format(new Date());

​ Use the ThreadLocal auxiliary class to provide a separate generator for each thread, but Java SE 7 also provides a more convenient class, the calling method is as follows:

int random = ThreadLocalRandom.current().nextInt(upperBound);

​ The ThreadLocalRandom.current() call will return an instance of the Random class specific to the current thread.

5.11 Lock test and timeout

​ When a thread calls the lock method to obtain a lock held by another thread, it is likely to block. You should apply for locks more carefully. The tryLock method attempts to apply for a lock and returns true after successfully acquiring the lock. Otherwise, it returns false immediately, and the thread can immediately leave to do other things. The lock method cannot be interrupted. If a thread is interrupted while waiting to acquire a lock, the interrupting thread remains blocked until the lock is acquired. If a deadlock occurs, the lock method cannot be terminated.

​ However, if you call tryLock with a timeout parameter, then if the thread is interrupted while waiting, an InterruptedException will be thrown. This is a very useful feature because it allows the program to break deadlocks. You can also call the lockInterruptibly method, which is equivalent to a timeout set to the wireless tryLock method.

6. Blocking queue


​ For many threading problems, it can be formalized in an elegant and safe way by using one or more queues. The producer thread inserts elements into the queue, and the consumer thread removes them. Using queues, you can safely pass data from one thread to another.

​ When trying to add elements to the queue and the queue is full, or trying to remove elements from the queue and the queue is empty, the blocking queue causes the thread to block. When coordinating the cooperation between multiple threads, blocking queues are a useful tool. Worker threads can periodically store intermediate results in the blocking queue. The other worker threads remove the intermediate results and modify them further. The queue will automatically balance the load. If the first thread set runs slower than the second, the second thread set will block while waiting for the result. If the first set of threads runs fast, it will wait for the second set of queues to catch up.

method Normal action Actions under special circumstances
add Add an element If the queue is full, an IllegalStateException is thrown
element Return the head element of the queue If the queue is empty, throw NoSuchElementException
offer Add an element and return true If the queue is full, return false
peek Return the head element of the queue If the queue is empty, return null
poll Move out and return the head element of the queue If the queue is empty, return null
put Add an element If the queue is full, block
remove Move out and return to the head element If the queue is empty, NoSuchElementException is thrown
take Move out and return to the head element If the queue is empty, block

​ Blocking queue methods are divided into the following three categories, depending on how they correspond when the queue is full or empty: If the queue is used as a thread management tool, the put and take methods will be used. When trying to add or remove elements from an empty queue, add, remove, and element operations throw exceptions. In a multithreaded program, the queue will be empty or full at any time, so be sure to use the offer, poll, and peek methods instead. If these methods cannot complete the task, they just give an error message without throwing an exception.

The poll and peek methods return null to indicate failure. Therefore, it is illegal to insert a null value into these queues!

7. Thread-safe collection


​ If multiple threads want to concurrently modify a data structure, such as a hash table, it is likely to destroy this data structure. You can protect shared data structures by providing locks, but it is easier to choose a thread-safe implementation as an alternative.

7.1 Efficient mapping tables, collections and queues

​ The java.util.concurrent package provides efficient implementation of mapping tables, ordered sets and queues: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet and ConcurrentLinkedQueue. These sets use sophisticated algorithms to minimize competition by allowing concurrent access to different parts of the data structure.

​ Unlike most collections, the size method of these collections does not have to operate in constant time. Determining the current size of such a collection usually requires traversal.

​ These collections return weakly consistent iterators. This means that iterators may not be able to show all the modifications after they were constructed. However, they will not return the same value twice, nor will they throw ConcurrentModificationException.

7.2 Write a copy of the array

​ CopyOnWriteArrayList and CopyOnWriteArraySet are thread-safe collections, in which all modification threads will copy the underlying array. When building an iterator, it contains a reference to the current array. If the array is modified later, the iterator still references the old array. Therefore, the old iterator has a consistent (possibly outdated) view, and access to it does not require any synchronization overhead.

7.3 Older thread-safe collections

​ Since the initial version of Java, the Vector and HashTable classes have provided thread-safe implementations of dynamic arrays and hash tables. These classes are now deprecated and replaced by the ArrayList and HashMap classes. These classes are not thread-safe, and different mechanisms are provided in the collection library. Any collection class can be used bySync wrapperBecome thread-safe:

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<>());
Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());

​ It should be avoided as much as possible that no thread accesses the data structure through the original asynchronous method. Therefore, when you need to use a thread-safe collection, it is best to simply construct a collection and pass it directly to the wrapper to obtain a thread-safe collection. .

It is better to use the collection defined in the java.util.concurrent package instead of using the synchronous wrapper.

Exception: When the array list to be used is frequently modified, the efficiency of synchronized ArrayList will be higher than CopyOnWriteArrayList.

8. Callable与Future


​ Runnable encapsulates a task that runs asynchronously. Think of it as an asynchronous method without parameters and return values. Callable is similar to Runnable, but Callable has a return value. The Callable interface is a parameterized type with only one method call.

public interface Callable<V>
{
    
    
    V call() throws Exception;
}
//类型参数是返回值的类型,如Callable<Integer>表示一个最终返回Integer对象的异步计算。

​ Future saves the results of asynchronous calculations. The owner of the Future object can obtain it after the result is calculated.

public interface Future<V>
{
    
    
    V get() throws ...;
    V get(long timeout,TimeUnit unit)throws ...;
    void cancel(boolean mayInterrupt);
    boolean isCancelled();
    boolean isDone();
}
//第一个get方法的调用被阻塞,直到计算完成。
//如果在计算完成前,第二个方法的掉用超时,则抛出一个TimeoutException异常。
//如果运行该计算的线程被中断,则两个方法都将抛出InterruptedException异常。
//如果计算已经完成,那么get方法立即返回。
//如果计算还在进行,调用isDone方法返回false,否则返回true。
//可以用cancel方法取消计算。如果计算还没开始,则它被取消不再开始。如果计算正在运行,则如果mayInterrupt参数为true,则中断计算。

9. Actuator


9.1 Thread Pool

​ There is a price to build a new thread, because it involves interaction with the operating system. If a large number of threads with a short lifetime are created in the program, you should useThread Pool. A thread pool contains many idle threads ready to run. Give the Runnable object to the thread pool, and a thread will call the run method. When the run method exits, the thread will not die, but will be ready to serve the next request in the pool.

​ Using thread pools can reduce the number of concurrent threads. Creating a large number of threads will greatly reduce performance or even crash the virtual machine.

​ The executor class (Executor) provides some static factory methods to build thread pools.

method description
newCachedThreadPool Create new threads when necessary; idle threads will be reserved for 60 seconds
newFixedThreadPool The pool contains a fixed number of threads; idle threads will always be reserved
newSingleThreadExecutor A thread pool with only one thread, which executes each submitted task sequentially
newScheduledThreadPool Fixed thread pool constructed for scheduled execution, instead of java.util.Timer
newSingleThreadScheduledExecutor Single-threaded "pool" built for scheduled execution

9.2 Scheduled execution

The ScheduledExecutorService interface has methods designed for scheduled execution or repeated execution of tasks. It is a generalization of java.util.Timer that allows the use of the thread pool mechanism. The newScheduledThreadPool and newSingleThreadScheduledExecutor methods of the Executors class will return objects that implement the ScheduledExecutorService interface.

10. Synchronizer


The java.util.concurrent package contains several classes that can help people manage the set of threads that cooperate with each other:

class use Applications
CyclicBarrier Allows the thread set to wait until a predetermined number of threads reach a common barrier, and then can choose to perform an action to deal with the barrier When a large number of threads need to complete before their results are available
CountDownLatch Allow the thread set to wait until the counter is reduced to 0 When one or more threads need to wait until a specified number of events occur
Exchanger Allows two threads to exchange objects when the objects to be exchanged are ready When two threads are working on two instances of the unified data structure, one adds data to the instance and the other clears data from the instance

​ These mechanisms have "with functions" provided for the shared assembly point mode between threads. If there is a cooperating thread set that satisfies one of these behavior patterns, then the appropriate library class should be reused directly instead of providing a manual lock and condition set.

10.1 Semaphore

​ One semaphore manages multiple licenses. In order to pass the semaphore, the thread requests permission by calling acquire. Other threads can release the license by calling release.

10.2 Countdown door latch

​ A countdown latch makes a set of threads wait until the count becomes 0. The countdown latch is one-time, once the count reaches 0, it cannot be reused.

10.3 Barrier

​ The CyclicBarrier class implements a gathering point: the barrier. Consider a situation where a large number of threads run in different parts of a calculation. When all parts are ready, the results need to be combined. When a thread has completed that part of its task, let it run to the barrier. Once all threads have reached this barrier, the barrier is cancelled and the threads can continue to run.

10.4 Switch

​ When two threads are working on two instances of the same data buffer, the switch can be used. The typical situation is: one thread fills the buffer with data, and another thread consumes the data. When they are all finished, exchange buffers with each other.

10.5 Synchronization queue

​ Synchronous queue is a mechanism for pairing producer and consumer threads. When a thread calls the put method of SynchronousQueue, it will block until another thread calls the take method, and vice versa. Unlike the switch, the data of the synchronous queue is only transferred in one direction, from the producer to the consumer.

Guess you like

Origin blog.csdn.net/qq_42026590/article/details/111911838