Processes and threads

Process: A running activity of a program on a data set. It is the basic unit of the operating system for resource allocation and scheduling.

Thread: The execution path of a process. There are multiple threads in a process. Multiple threads in a process share the resources of the process.

In Java, to start main is to start a JVM process, and the thread in which main is located is a thread in that process, called the master thread.

In the Java virtual machine, all threads share the method area and resources in the heap, and each thread has a private program counter and stack area.


Context switch

In multithreaded programming, the number of threads is generally greater than the number of cpus, and the CPU can only execute one thread at a time.

CPU resources are allocated using the time slice rotation strategy, that is, each thread is allocated a time slice, and the thread occupies the CPU to perform tasks in the time slice. When a thread runs out of time slices, it becomes ready and frees up the CPU for another thread to occupy this is a context switch

Examples of context switch timing:

  • The current thread is in the ready state when its CPU slice has been used up
  • When the current thread is interrupted by another thread

Is multithreading necessarily faster than single threading?

Not necessarily, single threads are faster than multithreads when the task volume is small because of the additional overhead of multithreaded context switching back


Thread state

There are six thread states defined in the Java language that can be converted between six different threads in different ways

  1. New (initial) : after the thread is created and not started
  2. Runnable: includes Running (the current thread is executing) and Ready (not executing yet, in a Ready state, waiting for the CPU to allocate a time slice for it)
  3. Waiting: threads in this state are not allocated a time slice by CPU scheduling. They are Waiting to be explicitly woken up by another thread
  4. Timed Waiting: A thread in this state is not allocated by CPU scheduling and wakes up automatically after a certain period of time, requiring no other thread to indicate that it is awake
  5. BLOCKED: When an object resource monitor lock is acquired, the lock is already occupied by another thread, and the thread enters the BLOCKED state
  6. Terminated: state of the thread

This diagram is very important, and the above methods will appear later


Java memory model

Working memory vs. main memory

The Java memory model specifies that all variables (instance variables, constants, static variables) are in main memory, and that each thread has its own working memory.

The working memory of a thread stores the main memory copy of variables used by the thread. All operations on variables must be carried out in the working memory of the thread, and data cannot be directly read or written in the main memory. Different threads cannot directly access variables in each other’s working memory, and the transfer of values between threads is done through the main memory.

The working memory here, which is abstract, doesn’t really exist. At the hardware level, it can be considered that the main memory is located on the main memory. The working memory covers the cache registers, controllers, arithmetic units and other hardware.


Intermemory operation

The Java memory model defines eight inter-memory interactions, each of which is atomic.

  1. Lock: Acts on variables in main memory, marking a variable as a thread-exclusive state
  2. Unlock: Works with a variable in main memory. It releases a locked variable before it can be locked by another thread
  3. Read: a variable acting on main memory that transfers the value of a variable from main memory to the thread’s working memory
  4. Load: variable applied to working memory, which puts the value of the variable obtained by the read operation from main memory into a copy of the variable in working memory.
  5. Use: variable applied to working memory, which passes the value of a variable in working memory to the execution engine. This operation is performed whenever the virtual machine reaches a bytecode instruction that assigns a value to the variable.
  6. Assign: a working memory variable that assigns a value received from the execution engine to the working memory variable. This operation is performed whenever the virtual machine accesses a bytecode instruction that assigns a value to the variable.
  7. Store: variable applied to working memory that passes the value of a variable in working memory to main memory for subsequent write operations
  8. Write: a variable operating on main memory that places the value of a variable in main memory from the store operation in working memory.

To copy a variable from main memory to working memory, perform the read and load operations.

To synchronize variables from working memory to main memory, store and write operations are performed

Rules to know:

  • A lock operation on a variable clears the value of the variable from working memory, and when the execution engine uses the variable, it needs to read it again from main memory

  • Before you can unlock a variable, you must synchronize the variable to main memory.


Three ways to create threads in Java

Inheriting the Thread

public class ThreadTest1 {
    public static class MyThread extends Thread{
        @Override
        public void run(a) {
            System.out.println("my first thread");
        }
        // The thread is in Terminated state after the run method is executed.
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread(); // The thread is in new state
        myThread.start(); // When the start method is called, the thread is in the Ready state. When the CPU schedules the thread, the thread is in the Running state}}Copy the code

Benefits:

To get the currentThread, use this instead of thread.currentthread ()

The bad:

Java only supports single inheritance. If you inherit from this class, you cannot inherit from another class


Implement the Runnable interface

public class ThreadTest2 {
    public static class RunableTask implements Runnable{
        @Override
        public void run(a) {
            System.out.println(Thread.currentThread().getName() + "2020-5-24, first day of learning multithreading."); }}public static void main(String[] args) {
        RunableTask task = new RunableTask();
        new Thread(task,"Diligence cures foolishness.").start(); // Give a name to thread
        new Thread(task,"Early Bird flies.").start(); }}Copy the code

Benefits: Solves inheritance problems

Implement Callable interface

Inheriting Thread directly and implementing Runnable have a common disadvantage of not getting the return value

public class ThreadTest3 {
    public static class MyTask implements Callable<String>{
        @Override
        public String call(a) throws Exception {
            System.out.println("Third way to create a thread");
            Thread.sleep(2000); // Thread sleep 2000ms
            return "77777"; Return a String}}public static void main(String[] args) {
        // Create an asynchronous task
        FutureTask<String> futureTask = new FutureTask<>(new MyTask());
        new Thread(futureTask).start(); // Start the thread
        try {
            String res = futureTask.get(); // The thread is in WAIT state until it finishes executing
            System.out.println(res); // Prints the return value
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("End of execution"); }}Copy the code


Notifications and waits for threads

The Object class contains notification and wait functions for threads

The wait function

Wait function before use must obtain object monitor lock, otherwise you will be thrown IllegalMonitorStateException

How do I get the monitor lock on an object? Use the synchronized keyword

Synchronized modifies code blocks with shared variables as parameters

// When an object's monitor lock is held by a thread, other threads attempting to acquire the object's monitor lock are blocked
synchronized(obj){
   // Only one thread can acquire the monitor lock of an object at a time. Other threads can attempt to acquire the lock only after the current thread releases the object lock
}
Copy the code

The wait function puts the current thread into the WAITNG state and releases the monitor lock on the object (other threads trying to acquire the lock will have a acquire lock) until the following event occurs:

  • Another thread calls notify or notifyAll of the shared object
  • Another thread calls the interrupt method of that thread, which throws InterruptedException and returns

notify&notifyAll

Notify function before use must also obtain the object’s monitor lock, otherwise you will be thrown IllegalMonitorStateException.

Call notify/notifyAll on the same object to wake up the threads waiting on monitor. The difference between Notify and notifyAll is that the former wakes up only one thread on monitor at random, while a notifyAll wakes up all threads.


Example 1

    private static Object resource = new Object(); // Define a shared object

    public static class NotifyTest implements Runnable{
        @Override
        public void run(a) {
            synchronized (resource) { // You must obtain the monitor lock of an object before using wait and notify
                String name = Thread.currentThread().getName(); // Get the current thread name
                System.out.println(name + Enter wait state to release lock resources);
                try {
                    resource.wait();
                    System.out.println(name + "Awakened");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "Execution complete, release lock resource"); }}}public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new NotifyTest(),"t1");
        Thread t2 = new Thread(new NotifyTest(),"t2");
        Thread t3 = new Thread(new NotifyTest(),"t3");
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000); // Wait for the previous thread to finish executing
        Threads T1, T2, and t3 are in wait state
        synchronized (resource){
           // resource.notify(); // Wake up a thread randomly
            resource.notifyAll(); // Wake up all threads}}Copy the code

Here t2 starts before T3, but T3 enters the Running state before T2: the ready state does not have to be executed first. You are always ready until the scheduler selects you. The sequence of results may not be the same each time the program is run

You can try notify for yourself and run it a few times to see what happens.


Example 2

If the current thread calls the wait method for a shared variable, the lock will only be released on the current shared variable. If the current thread holds locks on other contributed variables, the lock will not be released

private static Object resourceA = new Object();
    private static Object resourceB = new Object();  // Define two shared variables

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            String name = Thread.currentThread().getName();
            synchronized (resourceA){
                System.out.println(name + " get resourceA lock");
                synchronized (resourceB){
                    System.out.println(name + " get resourceB lock");
                    System.out.println(name + " release resourceA lock");
                    try {
                        resourceA.wait(); // Release the monitor lock for resourceA
                    } catch(InterruptedException e) { e.printStackTrace(); }}}},"t1").start();  // The thread name is T1
        
        Thread.sleep(1000); // Make sure t1 enters the Running state first
        
        new Thread(()->{
            String name = Thread.currentThread().getName();
            synchronized (resourceA){
                System.out.println(name + " get resourceA lock");
                System.out.println(name + " try get resourceB lock");
                synchronized (resourceB){
                    System.out.println(name + " get resourceB lock");
                    System.out.println(name + " release resourceA lock");
                    try {
                        resourceA.wait(); // Release the monitor lock for resourceA
                    } catch(InterruptedException e) { e.printStackTrace(); }}}},"t2").start();  // The thread name is T2
    }
Copy the code

T1 obtains the object lock of rescouceA, then the lock of recouceB (while thread T2 is not yet started). Then thread T1 releases the lock resource of object A and enters the WATI state. Thread T2 starts and acquires the object lock of rescouceA, but cannot acquire the object lock of resourceB because thread T1 has not released the object lock of resourceA.


join

In project practice, it is common to encounter a scenario where you need to wait for several things to complete before you can proceed. For example, when multiple threads load resources, they need to wait for all threads to complete loading before proceeding.

The Thread class has a join method that does this

The current thread will wait until the t thread finishes executing.

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 over");
        });

        Thread t2 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 over");
        });

        t1.start();
        t1.join(); // The current thread (main thread) enters the wait and must wait until thread T1 finishes executing

        t2.start();
        t2.join();  // The current thread (main thread) enters wait and must wait for t2 to finish

        System.out.println("main over"); }}Copy the code

sleep

The Thread class has a static method called sleep. When an executing Thread calls the Thread’s sleep method, the calling Thread temporarily cedes CPU execution rights for a specified period of time. This means that the Thread does not participate in CPU scheduling during this period, but does not release the locked resources.

public class SleepTest {
    private static Object resource = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            synchronized (resource) {
                try {
                    System.out.println("t1 get lock");
                    Thread.sleep(10000); // Enter the wait state without releasing the lock
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 over"); }}); Thread t2=new Thread(()->{
            synchronized (resource) {
                try {
                    System.out.println("t2 get lock");
                    Thread.sleep(10000); // Enter the wait state without releasing the lock
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 over"); }}); t1.start(); t2.start(); }}Copy the code

Notes written very clear, not post the results of the graph


yield

The Thread class has a static yield method that causes the currently executing Thread to yield the time slice and return to the ready state for the next CPU schedule. Not very often

public class YieldTest {
    static class MyTask implements Runnable{
        @Override
        public void run(a) {
            for (int i=0; i<3; i++){ String name = Thread.currentThread().getName();if (i==0){
                    Thread.yield(); // When I =0, the current thread abandons the time slice and the CPU starts the next schedule
                }
                System.out.println(name + " start "+ i); }}}public static void main(String[] args) {
        new Thread(new MyTask(),"t1").start();
        new Thread(new MyTask(),"t2").start();
        new Thread(new MyTask(),"t3").start(); }}Copy the code

When the yield method is invoked, it simply gives up the remaining slice of time for the current thread. Instead of being blocked and suspended, it is in a ready state, and the thread scheduler may still schedule the current thread for execution the next time.


interrupt

Interrupt means to interrupt, but it does not actually interrupt a thread. If the thread’s interrupt flag is set to true, the thread will continue to run

The Thread class has several functions related to interrupt

// Interrupt the thread
public void interrupt(a) {... }// Check whether the current thread is interrupted without clearing the interrupt flag
public boolean isInterrupted(a) {
    return isInterrupted(false);
}

// Static method to determine whether the current thread is interrupted, and clear the interrupt flag bit
public static boolean interrupted(a) {
    return currentThread().isInterrupted(true);
}

// Check whether the current thread is interrupted. Check whether the interrupt flag is cleared according to ClearInterrupted (true)
private native boolean isInterrupted(boolean ClearInterrupted);
Copy the code

Clearing the interrupt flag bit sets the interrupt flag bit to false. Let’s look at an example

public class InterrputTest {
    static class MyThread extends Thread {
        @Override
        public void run(a) {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {  // Change this to thread.interrupted ()
                    System.out.println("Thread interrupted");
                } else {
                    System.out.println("Thread running"); }}}}public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(1000); myThread.interrupt(); }}Copy the code

If (thread.interrupted ()) instead of ‘Thread running’ for 1s, then ‘Thread interrupted’, see if this changes.

Ps: Threads that are interrupted in sleep() or wait() throw InterruptedException.


Daemon threads and user threads

There are two types of threads in Java, daemon threads and user threads

The main thread is a user thread, and many daemon threads, such as the garbage collector thread, are also started inside the JVM.

The difference is that when the last user thread terminates, the JVM exits normally, regardless of whether the daemon thread terminates.

public class DaemonTest {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for(;;) {}// Infinite loop
        });

        t.setDaemon(true); // Set thread to daemon thread and comment out the line to see the differencet.start(); }}Copy the code

Although there is an infinite loop thread, it is the daemon thread and does not affect the exit of the JVM.


Original is not easy, ask for praise