Processes and threads

“Process”

The essence of a process is an executing program. While the program is running, the system will create a process and ** “allocate a separate memory address space for each process to ensure that the addresses of each process do not interfere with each other” **.

In addition, when the CPU switches time slices for a process, ensure that the process starts from the position where the process started before the switchover. So processes usually also include program counters and stack Pointers.

Relatively good understanding point of the case: the computer is open QQ is open a process, open IDEA is open a process, clear open viewer is open a process…..

When our computer opens too many of your applications (QQ, wechat, browser, PDF, Word, IDEA, etc.), the computer is prone to lag or even crash. The main reason is caused by the CPU constantly switching.

The following figure shows the switch between multiple processes on a single CPU:

With processes, the operating system can achieve multi-application concurrency at a macro level.

Concurrency is implemented by switching CPU time slices. For a single-core CPU, only one process is scheduled by the CPU at any one time.

The life cycle of a thread

Since it is a life cycle, there may be stages or states, such as human life:

❝ Sperm and egg combine –> baby –> child –> adult –> middle age –> old age –> death ❞

Thread state

There are different answers on the web about the life cycle of threads, ranging from five to six.

There are indeed six types of threads in Java, and this is justified by the fact that there is an enumeration in the java.lang.Thread class.

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED, 
        WAITING, 
        TIMED_WAITING, 
        TERMINATED;
}
Copy the code

This is the corresponding state of Java threads, which together represent the lifetime of a thread in Java. Here is a comment on this enumeration:

A brief description of each state:

  • NEW(initial) : The thread has not been started since it was created.
  • RUNNABLE: Includes Running and Ready in the operating system thread state, where a thread may be Running or waiting for system resources, such as a time slice allocated by the CPU.
  • BLOCKED: The thread is BLOCKED on the lock.
  • WAITING: a thread is WAITING for some specific action (notification or interrupt) from another thread.
  • TIME_WAITING: This state is different from WAITING in that it can return at a specified time.
  • TERMINATED: The thread is TERMINATED.

Thread life cycle

Borrow this picture from the Internet, this picture is very clear description, here is not wordy.

What is thread safety?

We often hear that a class is thread-safe and a class is not thread-safe. So what exactly does thread safety mean?

Let’s use the Java Concurrency in Practice definition:

❝ An object is thread-safe if it is accessed by multiple threads and the behavior of calling the object is correct, regardless of how the threads alternate or perform any other coordination on the caller, without additional synchronization. ❞

It can also be interpreted as:

❝ multiple threads access the same object, if don’t have to consider these threads in the runtime environment of scheduling and execution alternately, also do not need to undertake additional synchronization, or any other action on the caller, call the object’s behavior can get the right results, then the object is thread-safe. In other words, an interface provided by a class or program that is atomic to threads or that switches between threads does not result in ambiguities in the execution of the interface, which means we do not have to worry about synchronization. ❞

It can be simply understood as: “You call whatever you want, if there is a problem, I will lose”.

This definition is very strict for classes, and even classes labeled thread-safe by the Java API have a hard time meeting this requirement.

For example, Vector is marked thread-safe, but does not satisfy this condition. Here is an example:

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); } public synchronized void removeElementAt(int index) { modCount++; if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); } elementCount--; elementData[elementCount] = null; /* to let gc do its work */ } //.... Basically all methods are synchronized modified}Copy the code

Consider the following example:

Determine whether the 0th element in the Vector is a null character and delete it if it is.

package com.java.tian.blog.utils; import java.util.Vector; public class SynchronizedDemo{ static Vector<String> vct = new Vector<String>(); public void remove() { if("".equals(vct.get(0))) { vct.remove(0); } } public static void main(String[] args) { vct.add(""); SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo.remove(); }}," thread 1").start(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo.remove(); }}," thread 2").start(); }}Copy the code

The logic above looks fine, but in fact it can cause errors: assume that the 0th element is null, and the result is true.

Both threads execute the remove method above at the same time (” extreme case “), both “may” get “”, and then both remove the 0th element, which may have already been removed by another thread, so the Vector is not thread-safe. (The above example is just a demonstration; if you do this in your business code, you really can’t rely on Vector to ensure thread-safety).

Generally speaking, thread-safety is relative thread-safety. Relative thread-safety only requires that the correct result can be obtained without synchronization when calling a single method, but it is possible to cause multithreading problems when calling multiple methods in combination.

If we want the above operation to work correctly, we need to add an additional synchronization operation when we call the Vector method:

package com.java.tian.blog.utils; import java.util.Vector; public class SynchronizedDemo { static Vector<String> vct = new Vector<String>(); public void remove() { synchronized (vct) { //synchronized (SynchronizedDemo.class) { if ("".equals(vct.get(0))) { vct.remove(0); } } } public static void main(String[] args) { vct.add(""); SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo.remove(); }}, "thread 1").start(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo.remove(); }}, "thread 2").start(); }}Copy the code

According to the source code for Vector, each method of Vector uses the synchronized keyword modifier, so the lock object is the object itself. What we are trying to acquire in the above code is also the lock of the VCT object, which can be mutually exclusive with other methods of the VCT object, so it is guaranteed to get the correct result.

If the Vector is internally synchronized with another lock and encapsulates the lock object, then we cannot perform the “judge before modify” operation correctly anyway.

Suppose that the encapsulated object lock is OBj, the lock corresponding to get() and remove() methods is OBJ, and the entire operation process is VCT lock, a thread called get() successfully released obJ lock, this thread only holds VCT lock. Other threads can acquire obj’s lock and delete the 0th element first.

Java provides many powerful utility classes for developers, some of which are thread-safe and some of which are not. Here are a few common interview questions:

Thread-safe classes: Vector, Hashtable, StringBuffer

Non-thread-safe classes: ArrayList, HashMap, StringBuilder

One might ask: why doesn’t Java make all classes thread-safe? Isn’t that more fun for us developers? We don’t have to worry about thread safety.

There are two sides to the story, getting thread-safe but not performing well, because the overhead of locking is there. Threads are not safe but performance can improve, depending on which business is preferred.

A thought triggered by a question:

public class SynchronizedDemo { static int count; Public void incre() {try {// Each Thread takes a nap, imitates the business code thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } count++; } public static void main(String[] args) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { synchronizedDemo.incre(); } }).start(); Thread.sleep(2000L);} thread.sleep (2000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); }}Copy the code

The output of this code is indeterminate and is less than or equal to 1000.

1000 threads are going to ++ count.

Object memory layout

The memory storage of objects can be divided into three areas: object header, instance data, and alignment padding.

The object header contains two parts, one is the runtime data of the object itself, such as GC generation age, hash code, lock status identifier, etc., officially called “Mark Word”. If the influence of compression pointer is ignored, this part of the data occupies 32 bits and 64 bits respectively in 32-bit and 64-bit virtual machines.

However, objects need to store a lot of runtime data, which may not be able to be stored in 32 or 64 bits. Considering the space efficiency of virtual machines, this Mark Word is designed as an unfixed data structure, which will reuse its storage space according to the state of objects. When objects are in different states, The corresponding bit may have different meanings. For example, the 32-bit Hot Spot VM:

As can be seen from the figure above, if the object is in the unlocked state (unlocked state), 25 bits of the Mark Word are used to store the object’s hash code, 4 bits are used to store the age of the object, 1 bit is fixed as 0, and 2 bits are used to store the lock flag bit.

This diagram is very important to understand the light and partial locks mentioned later. Of course, we can now focus on the case of the object in the heavy lock state, that is, the lock flag bit is 10. At the same time, we can see that in both lock-free and biased lock-state, the 2-bit marker bit of the lock is “01”, and the remaining 1 bit indicates whether it can be biased. We can call it “biased bit”.

Note: The other part of the object header is a type pointer that the VIRTUAL machine can use to determine which class the object is an instance of. However, it is important to note that not all virtual machines must determine object metadata information in this way. There are two types of object access location: handle and direct pointer. If a handle is used, the metadata information of the object can be directly contained in the handle (of course, the address information of the object instance data), and there is no need to store the metadata and instance data together. Instance data and alignment padding are not discussed here.

As mentioned earlier, every object in Java is associated with a Monitor, and when the lock flag bit is 10, except for the 2bit flag bit, it points to the address of the Monitor object (again, using the 32-bit virtual machine as an example). If we need to download the OpenJDK source code, we can browse the OpenJDK source code.

To find it. Take a look at the markopp.hpp file first. The relative path of this file is:

openjdk\hotspot\src\share\vm\oops
Copy the code

Here is the comment section of the file:

We can see that it describes the storage state of Mark World in 32-bit and 64-bit. You can also see that in 64-bit, the first 25 bits are unused.

We can also see the enumeration of lock states defined in markoop.hpp, corresponding to the lock-free, bias, lightweight, heavyweight (expansion), GC flags we mentioned earlier:

Enum {locked_value = 0,//00 Lightweight lock unlocked_value = 1,//01 No lock Monitor_value = 2,//10 Heavyweight lock marked_value = 3,//11 GC tag Biased_lock_pattern = 5 //101 bias lock, 1 bit bias mark and 2 bit status mark (01)};Copy the code

From the comments, we can also see a brief description, which we will explain in detail later:

Objectmonitor.hpp is a header file with a path of: objectMonitor.hpp

openjdk\hotspot\src\share\vm\runtime
Copy the code

Let’s take a quick look at the objectMonitor.hpp definition:

// initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0,// Wait for threads _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; _WaitSetLock = 0; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; // List of threads waiting to acquire locks, with _EntryList using FreeNext = NULL; _EntryList = NULL ; // List of threads waiting to acquire locks, with _cxq using _SpinFreq = 0; _SpinClock = 0 ; OwnerIsThread = 0 ; // If the current owner is THREAD type, if the lightweight Lock bulking is not entered, //_owner may store the Lock Record _previous_owner_tid = 0; }Copy the code

Simply put, when multiple threads compete for access to the same synchronized code block, _owner is set to the current thread if the thread acquires monitor, _RECURsions are added if the monitor fails, and _CXQ is queued if the monitor fails.

When the lock is released, the thread in _CXQ is moved to _EntryList and wakes up the first thread in the _EntryList queue. Of course, there are several different strategies for selecting a wake up thread (Knob_QMode), and the source code will be interpreted later.

“Note” : _CXq and _EntryList are essentially ObjectWaiter types. They are essentially bidirectional lists (with front and back Pointers), but are not necessarily used as bidirectional lists. For example, _CXq is used as a one-way list, and _EntryList is used as a bidirectional list.

What scenarios can cause context switching of threads?

There are two types of thread context switches:

A spontaneous context switch is a thread that is cut out by a Java program call. In multithreaded programming, executing a call to a method or keyword in the figure above often causes a spontaneous context switch.

An involuntary context switch is a thread that is being pushed out due to the scheduler. Common examples are threads running out of allocated time slices, virtual machine garbage collection, or execution priority issues.

waity /notify

Note the two queues:

  • Wait queue: notifyAll/notify wakes up the thread in the wait queue.
  • Synchronous thread: all the threads competing for the lock, waiting for the thread in the queue to wake up and enter the synchronization queue.

What is the difference between sleep and wait

sleep

  • Hibernates the current thread for a specified time.
  • The accuracy of sleep time depends on the system clock and CPU scheduling mechanism.
  • If the sleep method is called in the synchronization context, no other thread can enter the current synchronization block or method without releasing the acquired lock resource.
  • A dormant thread can be woken up by calling the interrupt() method.
  • Sleep is the method in Thread

wait

  • Put the current thread into the wait state, and when other threads call notify() or notifyAll(), the current thread enters the ready state
  • The wait method must be called in a synchronous context, such as a synchronized method block or a synchronized method. This means that if you want to call the wait method, you must obtain the lock resource on the object
  • When the WAIT method is called, the current thread releases the acquired lock resource and enters the wait queue, so that other threads can attempt to acquire the lock resource on the object.
  • Wait is a method in Object

Optimistic lock, pessimistic lock, reentrant lock…..

As someone who has been Java development for many years, you must be somewhat familiar with locks, or have heard of locks. Let’s do a lock summary today.

Pessimistic locks and optimistic locks

Pessimistic locking

If thread A acquires the lock, thread B will have to wait in line for thread A to release the lock.

To do this in the database:

Select user_name, user_pwd from t_user for update;Copy the code

Optimistic locking

As the name implies, optimism, people optimistic is what is open, cross the bridge when you come to it. Optimistic lock is that I feel that they did not get the lock, only I got the lock, and then finally ask if the lock is really me? If I do, I’ll do it.

The resource is not the one I saw before, so I will replace it with mine. If not, forget it.

In Java. Java util. Concurrent. Atomic package this atomic variables is to use the optimistic locking of a way to achieve the CAS.

You usually use version, timestamp, and so on to compare whether it has been modified by another thread.

Use pessimistic locks or optimistic locks?

In the optimistic lock and pessimistic lock choice above, mainly look at the difference between the two and the applicable scene can be.

“Response efficiency”

If very high response speed is required, an optimistic locking scheme is recommended. If it succeeds, it fails. There is no need to wait for other concurrency to release the lock. Optimistic lock does not really lock, high efficiency. If the granularity of the lock is not well controlled, the probability of update failure is high, which leads to service failure.

“Conflict frequency”

If the conflict frequency is very high, pessimistic locking is recommended to ensure the success rate. If the conflict frequency is large, the optimistic lock will need several retries to succeed, and the cost is high. “Retry cost”

Pessimistic locking is recommended if the retry cost is high. Pessimistic locks rely on database locks and are inefficient. The probability of update failure is low.

Optimistic lock If someone updates before you, your update should be rejected and the user can start again. Pessimistic locks wait for the previous update to complete. That’s the difference.

Fair locks and unfair locks

Fair lock

As the name suggests, is fair, first come, first served, FIFO; Queuing rules must be observed. Do not trespass. Multiple threads obtain locks in the order they apply for locks, and the threads directly enter the queue to queue up, always being the first in the queue to get locks.

An unfair lock used by default in ReentrantLock, but can be specified as a fair lock when building an instance of ReentrantLock.

ReentrantLock fairSyncLock = new ReentrantLock(true);
Copy the code

Assuming that thread A has already held the lock, thread B’s request for the lock will be suspended. When thread A releases the lock, if thread C also needs to acquire the lock, then in fair lock mode, the steps of acquiring and releasing the lock are as follows:

  1. Thread A obtains the lock –> Thread A releases the lock
  2. Thread B acquires lock –> thread B releases lock;
  3. Thread C acquires lock –> thread releases lock;

“Advantages”

All threads get resources and don’t starve to death in the queue.

“Shortcomings”

Throughput drops so much that all but the first thread in the queue is blocked, and it’s expensive for the CPU to wake up the blocked thread.

Not fair lock

As the name suggests, I don’t care which one of you lines up first, which is the one you hate in life. There are many queues in life, such as getting on the bus, taking the elevator, paying at the supermarket and so on. But not everyone will obey the rules and stand in line, which makes it unfair for those who stand in line. Wait until you can’t get in line.

When multiple threads try to obtain the lock, they will directly try to obtain it. If they cannot obtain it, they will enter the waiting queue. If they can obtain it, they will directly obtain the lock.

ReentrantLock uses unfair locks by default in two ways:

ReentrantLock fairSyncLock = new ReentrantLock(false);
Copy the code

Or:

ReentrantLock fairSyncLock = new ReentrantLock();
Copy the code

Can implement unfair locks.

“Advantages”

It can reduce the overhead of the CPU to wake up threads, the overall throughput efficiency will be higher, the CPU does not have to wake up all threads, will reduce the number of threads to wake up.

“Shortcomings”

As you may have noticed, this can cause the thread in the middle of the queue to never acquire the lock or for a long time, leading to starvation.

Exclusive locks and shared locks

Exclusive lock

An exclusive lock, also known as an exclusive lock/mutex, means that the lock can only be held by one thread lock at a time. If thread T holds an exclusive lock on data A, no other thread can hold any type of lock on data A. The thread that acquires the exclusive lock can both read and modify data. The implementation classes for synchronized in the JDK and Lock in JUC are mutex.

A Shared lock

A shared lock means that the lock can be held by multiple threads. If thread T adds A shared lock to data A, other threads can only add A shared lock to data A, but not an exclusive lock. The thread that acquires the shared lock can only read the data, not modify it.

In the case of ReentrantLock, this is the exclusive lock. But ReadWriteLock, another implementation of Lock, has a shared read Lock and an exclusive write Lock.

  1. The shared lock of read lock ensures that concurrent read is very efficient. Read, write, read, and write processes are mutually exclusive.
  2. Exclusive locks and shared locks are also passedAQSBy implementing different methods to achieve exclusivity or sharing.

Reentrant lock

If a lock has been acquired in the current thread execution, it will not be blocked if the lock is acquired again.

public class RentrantLockDemo { public synchronized void test(){ System.out.println("test"); } public synchronized void test1(){ System.out.println("test1"); test(); } public static void main(String[] args) { RentrantLockDemo rentrantLockDemo = new RentrantLockDemo(); New Thread(() -> rentrantlockdemo.test1 ()).start(); }}Copy the code

When a thread executes the test1() method, it needs to acquire the rentrantLockDemo object lock. The test1 method summary calls the test method, but the test() call requires the object lock.

A reentrant lock, also known as a “recursive lock”, is a lock acquired by an outer function of the same thread, but the inner recursive function still has the code to acquire the lock, but is not affected.

ThreadLocal design principles

A ThreadLocal variable is a ThreadLocal variable.

Let’s take a look at ThreadLocal in its entirety:

The three public methods of most concern: set, get, and remove.

A constructor

 public ThreadLocal() {
 }
Copy the code

There is no logic in the constructor, it simply creates an instance.

Set method

The source code for:

Public void set(T value) {public void set(T value) { // What the hell is this? ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); }Copy the code

Let’s see what ThreadLocalMap is:

ThreadLocalMap is a static inner class of ThreadLocal.

The whole set method is:

ThreadLocalMap constructor:

Private final int threadLocalHashCode = nextHashCode(); private final int threadLocalHashCode = nextHashCode; private Entry[] table; private static final int INITIAL_CAPACITY = 16; Static class Entry extends WeakReference<ThreadLocal<? >> { Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Array default size is 16 table = new Entry[INITIAL_CAPACITY]; / / len is 2 n to the calculation of ThreadLocal hash value according to the Entry of the [] modulus (hash) in order to better int I = firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; // Set the threshold (capacity expansion threshold) setThreshold(INITIAL_CAPACITY); }Copy the code

Then let’s look at what happens in the map.set() method:

private void set(ThreadLocal<? > key, Object value) { Entry[] tab = table; int len = tab.length; Int I = key.threadLocalHashCode & (len-1); If the current thread has an Entry[I] for (Entry e = TAB [I]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); If (k == key) {e.value = value; if (k == key) {e.value = value; return; } key:null value:xxxObject; // We need to clean up the original value so that we can garbage collect the value and put the new value in the slot. End if (k == null) {replaceStaleEntry(key, value, I); return; }} // No ThreadLocal uses Entry[I] and stores values TAB [I] = new Entry(key, value); Int sz = ++size; // Clear the Entry whose key is null. You may need to expand the size of the Entry twice as long as the original one, and rehash if (! cleanSomeSlots(i, sz) && sz >= threshold){ rehash(); }}Copy the code

From the set method above, we can roughly associate these three:

Thread, ThreadLocal, ThreadLocalMap.

The get method

The remove method

The expungeStaleEntry method is a little big in the code, so it’s posted here.

Private int expungeStaleEntry(int staleSlot) {entry [] TAB = table; int len = tab.length; tab[staleSlot].value = null; // Delete value TAB [staleSlot] = null; // Delete entry size--; // Map size decreases // traverses the specified node to be deleted. All subsequent nodes Entry E; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); If (k == null) {// if key is null, execute the delete operation e.value = null; tab[i] = null; size--; } else {//key is not null, recalculate the subscript int h = k.htReadLocalHashCode & (len-1); if (h ! = I) {// if not in the same position TAB [I] = null; // While (TAB [h]!) insert while (TAB [h]! = null){ h = nextIndex(h, len); } tab[h] = e; } } } return i; }Copy the code