1, thread and process relationship

Fundamental difference: Processes are the basic unit of operating system resource allocation, while threads are the basic unit of processor task scheduling and execution.

Resource overhead: Each process has its own code and data space (program context), and switching between programs can be expensive; Threads can be regarded as lightweight processes. The same type of threads share code and data space, and each thread has its own independent running stack and program counter (PC). Switching between threads has little overhead.

Inclusion: If there are multiple threads in a process, the execution process is not one line, but multiple lines (threads). Threads are part of a process, so they are also called lightweight or lightweight processes.

Memory allocation: Threads of the same process share the address space and resources of the process, while the address space and resources of the process are independent of each other.

Impact relationship: the crash of one process in protected mode has no impact on other processes, but the crash of one thread kills the entire process. So multi-processing is more robust than multi-threading.

2. What states do threads have

A. Create (new) : a thread object is created.

B. Runnable: After a thread object is created, other threads (such as the main thread) call its start() method. Threads in this state are in the runnable thread pool, waiting to be selected by thread scheduler for CPU usage.

C. Running: Runnable threads get timeslice and execute program code.

D. Block: A blocked state is when a thread gives up CPU usage for some reason, i.e. gives up CPU timeslice and temporarily stops running. Until the thread enters the runnable state, it has no chance to get the CPU timeslice again and go to the running state. There are three types of blocking:

(I). Waiting to block: The running thread executes O.wait () and THE JVM places the thread in waitting Queue.

When a running thread acquires a lock on an object and the lock is held by another thread, the JVM adds that thread to the lock pool.

Sleep (long ms) or t.join(), or when an I/O request is issued, the JVM blocks the running Thread. When the sleep() state times out, when the join() wait thread terminates or times out, or when I/O processing is complete, the thread returns to the runnable state.

E. Dead: The run() and main() methods of a thread end, or the run() method exits abnormally, and the thread ends its life cycle. Dead threads cannot be resurrected.

Lock pools and wait pools

In Java, each object has two pools, a monitor pool and a wait pool

Lock pool: Suppose thread A already owns the lock on an object (not A class), and other threads want to call A synchronized method (or block) of that object. Since these threads must acquire ownership of the lock before they can access the synchronized method of the object, However, the lock on this object is currently owned by thread A, so these threads enter the lock pool on this object.

Waiting for the pool: If thread A calls the wait() method of an object, thread A releases the lock on that object (since A wait() must occur in synchronized, thus owning the lock before executing its wait()), and thread A enters the object’s wait pool. If another thread calls notifyAll() on the same object, all the threads in the object’s wait pool enter the lock pool, ready to compete for ownership of the lock. If another thread calls notify() on the same object, only one thread in the wait pool of that object (randomly) will enter the lock pool of that object.

3. Basic implementation principle of synchronized

Three applications of synchronized

Modifier instance method: locks the current object before entering the synchronization code.

Modifies static methods that lock the current class before entering synchronized code.

When synchronized acts on a static method, the lock is the class object lock of the current class. Because static members are not exclusive to any instance object and are class members, concurrent operations on static members can be controlled through class object locks. Note that if thread A calls the non-static synchronized method of an instance object and thread B calls the static synchronized method of the class that the instance object belongs to, the mutual exclusion will not occur. Because a lock used to access a static synchronized method is the current class object, and a lock used to access a non-static synchronized method is the current instance object lock.

Modify code block: specifies the lock object, locks the given object, and obtains the lock for the given object before entering the synchronous code base.

Underlying principle of synchronized

Synchronization in Java virtual machines is implemented based on incoming and outgoing Monitor objects, whether it is explicit (with explicit Monitorenter and Monitorexit directives, i.e. synchronized code blocks) or implicit. In the Java language, synchronization is probably the most commonly used synchronization method modified by synchronized.

In the JVM, objects are laid out in memory in three areas: object headers, instance data, and aligned padding

Padding data: The vm requires that the start address of the object be a multiple of 8 bytes. Padding data doesn’t have to be there, just for byte alignment.

As for the top, it is the Java header object, which realizes the basis of synchronized lock object. We focus on it. Generally speaking, synchronized lock object is stored in the Java object head. The Mark Word and the Class Metadata Address are the Mark Word and the Class Metadata Address.

A pointer points to the starting address of a Monitor object, also known as a pipe or monitor lock. Each object has a Monitor associated with it, and the relationship between the object and its Monitor can be implemented in various ways. For example, the monitor can be created and destroyed together with the object or automatically generated when a thread tries to acquire an object lock. However, when a monitor is held by a thread, it is locked. In the Java Virtual Machine (HotSpot), Monitor is implemented by ObjectMonitor:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // Number of records
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // Threads in wait state are added to _WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // Threads in the waiting block state are added to the list
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

Copy the code

ObjectMonitor has two queues, _WaitSet and _EntryList, that hold the list of ObjectWaiter objects (each thread waiting for a lock is encapsulated as an ObjectWaiter object). _owner refers to the thread that holds the ObjectMonitor object. When multiple threads simultaneously access a piece of synchronized code, they first enter the _EntryList collection. When a thread obtains the object’s monitor, it enters the _Owner area, sets the owner variable in monitor to the current thread, and increases the count in monitor by 1. If the thread calls wait(), it will release the currently held monitor. The owner variable returns to null, count decreases by 1, and the thread enters the WaitSe T collection to be woken up. If the current thread completes, it will also release the monitor and reset the value of the variable so that other threads can enter and acquire the monitor.

3: monitorenter  // Enter the synchronization method
/ /... Omit the other
15: monitorexit   // Exit the synchronization method
16: goto          24
// omit other.......
21: monitorexit // Exit the synchronization method
Copy the code

It can be seen from the bytecode that the synchronized statement block is implemented using monitorenter and Monitorexit instructions. Monitorenter refers to the starting position of the synchronized code block, and Monitorexit refers to the ending position of the synchronized code block. When monitorenter executes, the current thread attempts to acquire the objectref’s monitor. If the objectref’s monitor’s entry counter is 0, the thread succeeds in acquiring the Monitor. And set the counter value to 1, the lock is successfully obtained. If the current thread already owns objectref’s Monitor, it can re-enter the monitor (more on reentrancy later) and the counter will be incresed by one. If another thread already owns objectref’s monitor, the current thread will block until the executing thread completes, i.e. the monitorexit directive is executed, which releases the monitor(lock) and sets the counter to 0. Other threads will have the opportunity to own monitor. Note that the compiler will ensure that regardless of how the method completes, every Monitorenter directive called in the method executes its monitorexit counterpart, regardless of whether the method terminates normally or abnormally.

To ensure that monitorenter and Monitorexit can be paired correctly when the method exception completes, the compiler automatically generates an exception handler that claims to handle all exceptions. The exception handler is intended to execute monitorexit. You can also see from the bytecode that there is an additional Monitorexit directive, which is the monitorexit directive that is executed to release monitor when the exception ends.

4. Difference between synchronized and Lock

A, Synchronized is the keyword, the built-in language implementation, Lock is the interface.

B, Synchronized will automatically release the lock when an exception occurs in the thread, so abnormal deadlock will not occur. Lock exceptions do not automatically release the Lock, so you need to do so in finally.

C. Lock is an interrupt Lock, Synchronized is a non-interrupt Lock and must wait for the thread to complete the Lock release.

D, Lock can use read Lock to improve multithreaded read efficiency.

1, The difference between synchronized and lock

Synchronized: Adds this control to the object to be synchronized. Synchronized can be added to a method or to a specific block of code, with parentheses indicating the object to be locked.

Lock: The specified start and end positions need to be displayed. Generally, the ReentrantLock class is used as the lock. In order for the lock to take effect, one ReentrantLock class must be used as the object in multiple threads. Lock () and unlock() must be used to indicate lock and unlock. So it’s common to write unlock() ina finally block to prevent deadlocks.

Usage differences are relatively simple, here is not repeated, if you do not understand the basic Java syntax.

2. Performance difference between synchronized and lock

Synchronized is managed for JVM execution, while Lock is code written by Java to control locks. In Java1.5, synchronize is inefficient. Because this is a heavyweight operation that calls the operation interface, it is possible that locking will consume more system time than operations other than locking. By contrast, using Java provided Lock objects provides better performance. But with Java1.6, something changed. Synchronize is semantically clear and allows for many optimizations, including adaptive spin, lock cancellation, lock coarser, lightweight lock, biased lock, and so on. As a result, performance on Java1.6 is no worse than that of Lock in synchronize. Officials also say they support Synchronize more, which could be improved in future releases.

At this point, I’d like to mention the specific differences between the two mechanics. As far as I know, synchronized originally used the CPU pessimistic locking mechanism, where a thread acquired an exclusive lock. An exclusive lock means that other threads must rely on blocking to wait for the thread to release the lock. However, when the CPU conversion thread is blocked, it will cause the thread context switch, and when there are many threads competing for the lock, it will cause the CPU frequent context switch, resulting in low efficiency.

Lock uses optimistic locking. Optimistic locking is when an operation is performed each time without locking, assuming no conflicts, and then retry until it succeeds if it fails because of conflicts. The mechanism of optimistic locking implementation is CAS operation (Compare and Swap). We can look further into the source code for ReentrantLock and see that one of the more important methods of obtaining the lock is compareAndSetState. This is essentially a special instruction provided by the calling CPU.

Modern cpus provide instructions to automatically update shared data and detect interference from other threads, which compareAndSet() uses instead of locking. This algorithm is called a non-blocking algorithm, meaning that the failure or suspension of one thread should not affect the failure or suspension of other threads.

5, how to prevent deadlock?

Deadlocks may occur when a process is running, but certain conditions must be met for the occurrence of deadlocks. The following four conditions must be met.

1) Mutually exclusive conditions: a process uses the allocated resources in an exclusive manner, that is, only one process occupies a certain resource in a period of time. If another process requests the resource at this time, the requester can only wait until the process holding the resource is released.

2) Request and hold conditions: the process has held at least one resource, but it puts forward a new resource request, and the resource has been occupied by other processes. In this case, the requesting process blocks, but does not release other resources it has obtained.

3) Non-deprivation condition: the process can not be deprived of the resources it has acquired before they are used up, but can only release them when they are used up.

4) Loop waiting condition: when deadlock occurs, there must be a process — resource loop chain, that is, P0 in process set {P0, P1, P2, ··· Pn} is waiting for a resource occupied by P1; P1 is waiting for resources occupied by P2…… Pn is waiting for a resource that has been occupied by P0.

After deadlocks occur in the system, the system should detect the occurrence of deadlocks in time and take appropriate measures to remove deadlocks. The current methods for dealing with deadlocks can be summarized as follows:

  1. Prevent deadlocks.

This is a simple and intuitive way to prevent beforehand. The idea is to prevent deadlocks by setting restrictions that break one or more of the four necessary conditions for deadlocks to occur. Deadlock prevention is an easy method to implement and has been widely used. However, the restrictions imposed are often too strict, which may lead to a decrease in system resource utilization and system throughput.

  1. Avoid deadlocks.

This method is also a precautionary strategy, but it does not need to take a variety of prior restrictions to destroy the four necessary conditions of deadlock, but in the process of dynamic allocation of resources, some way to prevent the system from entering the insecure state, so as to avoid deadlock.

3) Detect deadlocks.

This method does not require any prior restrictive measures and does not need to check whether the system has entered the insecure zone. This method allows the system to issue life and death locks during operation. However, deadlock can be detected in time through the detection mechanism set up in the system, and precisely determine the process and resources related to deadlock, and then take appropriate measures to clear the deadlock from the system.

4) Unlock deadlocks.

This is a measure that works with deadlock detection. When a deadlock is detected in the system, remove the process from the deadlock state. A common implementation is to cancel or suspend some processes in order to reclaim some resources, which are then allocated to a blocked process to move it to a ready state to continue running. Deadlock detection and release measures may make the system get better resource utilization and throughput, but it is also the most difficult to implement.

What are the states of the thread pool?

1.RUNNING: This is the most normal state, accepting new tasks and processing tasks in the waiting queue. The initial state of the thread pool is RUNNING. Once created, the thread pool is in the RUNNING state and the number of tasks in the pool is 0.

2.SHUTDOWN: No new task is submitted, but the task in the waiting queue continues to be processed. The thread pool is called with RUNNING -> shutdown when the shutdown() method is called.

3.STOP: does not accept new task submissions, stops processing tasks in the waiting queue, and interrupts the thread executing the task. The thread pool shutdownNow() method is called with (RUNNING or SHUTDOWN) -> STOP.

4.TIDYING: All tasks are destroyed, workCount is 0, and the thread pool state terminated() executes the hook method when it transitions to TIDYING state. Because terminated() is empty in the ThreadPoolExecutor class, the user wants to process the thread pool when it becomes TIDYING; This can be done by overloading the terminated() function.

When the thread pool is SHUTDOWN, the blocking queue is empty, and the tasks executed in the thread pool are empty, SHUTDOWN -> TIDYING is called.

When the thread pool is in the STOP state and the task executed in the thread pool is empty, STOP -> TIDYING is invoked.

TERMINATED: the thread pool is in TIDYING state, and TERMINATED () is TERMINATED by TIDYING -> TERMINATED.

Use the four threads provided by Executors for the thread pool. 1. NewCachedThreadPool Create a cacheable thread pool. If the thread pool length exceeds processing requirements, recycle idle threads. 2. NewFixedThreadPool Creates a fixed-length thread pool that controls the maximum number of concurrent threads. The excess threads will wait in the queue. 3. NewScheduledThreadPool Creates a thread pool of fixed length that supports scheduled and periodic task execution. 4. Create a single threaded newSingleThreadExecutor thread pool, it will only use the only worker thread to perform the task, to ensure all tasks in specified order (FIFO, LIFO, priority).

1. NewCachedThreadPool Creates a cacheable thread pool. If the length of the thread pool exceeds the required length, you can recycle idle threads. The sample is as follows

ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0; i<5; i++){final int index = i;
    try {
        Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    executorService.execute(new Runnable() {
        @Override
        public void run(a) {
            System.out.println(Thread.currentThread().getName() +  ","+index); }}); }Copy the code

// Console information Pool-1-thread-1,0 pool-1-thread-1,1 pool-1-thread-1,2 pool-1-thread-1,3 pool-1-thread-1,4 2. NewFixedThreadPool Creates a fixed-length thread pool that controls the maximum number of concurrent threads. The excess threads will wait in the queue. The sample is as follows

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
for(int i=0; i<5; i++) {final int index = i;
       fixedThreadPool.execute(new Runnable() {
        @Override
        public void run(a) {
            try {
                System.out.println(Thread.currentThread().getName() + "," + index);
                Thread.sleep(2000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}); }Copy the code

// Console information Pool-1-thread-1,0 pool-1-thread-2,1 pool-1-thread-3,2 pool-1-thread-4,3 pool-1-thread-1,4 3. NewScheduledThreadPool Creates a thread pool of fixed length. The following example supports periodic and scheduled tasks

ScheduledExecutorService scheduledThreadPool =  Executors.newScheduledThreadPool(5);
System.out.println("before:" + System.currentTimeMillis()/1000);
scheduledThreadPool.schedule(new Runnable() {
    @Override
    public void run(a) {
        System.out.println("Execution delayed by 3 seconds :" + System.currentTimeMillis()/1000); }},3, TimeUnit.SECONDS);
System.out.println("after :" +System.currentTimeMillis()/1000);
Copy the code

// Console information before:1518012703 after :1518012703 Delay 3 seconds oh :1518012706

System.out.println("before:" + System.currentTimeMillis()/1000);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run(a) {
        System.out.println("Execute every 3 seconds after 1 second delay :" +System.currentTimeMillis()/1000); }},1.3, TimeUnit.SECONDS);
System.out.println("after :" +System.currentTimeMillis()/1000);
Copy the code

Console message before:1518013024 after :1518013024 Delay after 1 second, execute once in 3 seconds :1518013025 delay after 1 second, execute once in 3 seconds :1518013028 delay after 1 second, 3 seconds to perform a: 1518013031 4. Create a single threaded newSingleThreadExecutor thread pool, only can use worker threads to perform a task, guarantee the order, the sample is as follows

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i=0; i<10; i++) {final int index = i;
    singleThreadExecutor.execute(new Runnable() {
        @Override
        public void run(a) {
            try {
                  System.out.println(Thread.currentThread().getName() + "," + index);
                Thread.sleep(2000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}); }Copy the code

// Console information Pool-1-thread-1,0 pool-1-thread-1,1 pool-1-thread-1,2 pool-1-thread-1,3 Pool-1-thread-1,4 Submits tasks to the thread pool Execute () is an Executor method declared in ThreadPoolExecutor and implemented in ThreadPoolExecutor. This method is the core method of ThreadPoolExecutor, and allows you to submit a task to a thread pool for execution.

The submit() method is declared in the ExecutorService, AbstractExecutorService is implemented, and is not overwritten in ThreadPoolExecutor. It is also used to submit tasks to a thread pool. Unlike the execute() method, however, it can return the result of the execution of the task. If you look at the implementation of the submit() method in the source code, you will see that it is actually the execute() method called, but it uses the Future to retrieve the result of the execution of the task.

/ * * *@throws RejectedExecutionException {@inheritDoc}
 * @throws NullPointerException       {@inheritDoc} * /
publicFuture<? > submit(Runnable task) {if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
Copy the code

The thread pool can be shutdown by calling the shutdown or shutdownNow methods of the thread pool, but they work differently. Shutdown simply sets the state of the thread pool to shutdown, and then interrupts all threads that are not executing tasks. ShutdownNow works by iterating through worker threads in a thread pool and then interrupting them one by one by calling the thread_interrupt method, so tasks that cannot respond to interrupts may never be terminated. ShutdownNow first sets the state of the thread pool to STOP, then attempts to STOP all threads executing or suspending tasks and returns a list of tasks waiting to be executed.

The isShutdown method returns true whenever either of the two shutdown methods is called. The thread pool is closed successfully when all tasks are closed, and calling isTerminaed returns true. Which method we should call to shutdown the thread pool depends on the nature of the task submitted to the thread pool. Shutdown is usually called to shutdown the thread pool, or shutdownNow if the task is not necessarily finished.

  1. The main workflow of the thread pool is as follows: Java thread pool workflow

From the above figure, we can see that when a new task is submitted to the thread pool, the thread pool process is as follows:

First, the thread pool determines whether the base thread pool is full. No, create a worker thread to execute the task. If it is full, it goes to the next process. Second, the thread pool determines whether the work queue is full. If not, the newly submitted task is stored in the work queue. If it is full, it goes to the next process. Finally, the thread pool determines whether the entire thread pool is full. If it is not full, a new worker thread is created to execute the task, and if it is full, the saturation policy is assigned to handle the task. ** Source analysis. ** The above process analysis let us have a very intuitive understanding of the working principle of the thread pool, let’s look at the source code to see how it is implemented. The thread pool performs tasks as follows:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null.false);
    }
    else if(! addWorker(command,false))
        reject(command);
}
Copy the code