Multithread Foundation in Java

Posted by brucemalti on Tue, 08 Oct 2019 22:01:43 +0200

Threads and processes

Process:

  • Process is the basic unit of program operation and resource allocation. A program has at least one process.
  • As shown in the following figure:

 

Threading:

  • Thread is the basic unit of CPU scheduling and allocation. A process has at least one thread.
  • Threads in the same process share process resources (reduce switching, improve efficiency), and can be executed concurrently.

2. Concurrent and Parallel

Concurrency:

  • It refers to the execution of multiple events at the same time interval, emphasizing that multiple different things can be done over a period of time.
  • For example, in a minute, take a bite of rice and drink a mouthful of water, then say a word, etc.

Parallel:

  • It refers to the execution of multiple events at the same time, emphasizing that multiple different things can be done at the same time.
  • For example, while eating, listening to songs and watching TV.

3. Context switching of threads

Basic principles:

  • A CPU can only execute one thread at any time. If multiple threads (more than the number of CPUs) exist, the CPU will switch threads in a time slice rotation way. That is to say, each thread will be allocated a time slice. When a thread's time slice is exhausted, it will be ready and let the CPU be used by other threads. This is a context switch.
  • Context switching stores the running state of each thread through the program counter in the run-time data area, so that the next time the thread runs, it can continue to run the last instruction (for example, when thread A makes a calculation to return the data, it switches to thread B, and then cuts back to thread A, it returns the data directly instead of calculating again).
  • Program counter refers to a memory area in the JVM. It can be regarded as a line number indicator of the bytecode executed by the current thread. Through it, Java can know which step instructions are executed by each thread (thus it can be seen that the program counter is thread-private).
  • Generally speaking, context switching is a CPU-consuming operation.

4. Several states of threads

Basic principles:

  • Create: This refers to the generation of thread objects, at which point the start method is not invoked.
  • Ready: After calling the start method of the thread, the thread enters the ready state and waits for the system to schedule.
  • Running: Start running run functions in threads through system scheduling.
  • Wait: Calling Object.wait() will enter the waiting queue. Typically, you need to wait for other threads to make specific notifications or interrupts.
  • Time-out Waiting: This state is different from the wait state in that it returns automatically, such as calling Threa.sleep(long), after which it returns automatically to the ready state.
  • Blocking: When a lock is acquired (waiting to enter a synchronized method or block), it is found that the synchronized lock is occupied, and then the thread is put into the lock pool, waiting to acquire the lock.
  • Death: After running the run method, the main method (main method refers to the main thread) exits normally, and there may be an exception leading to death; the dead thread can not be re-enabled, otherwise an error will be reported.

5. Several ways to create threads

Inherit the Thread class:

  • Create a class that inherits Thread and overrides the run method, the thread executor.
  • Create a subclass instance that inherits the Thread class, the thread object
  • Invoke the start() method of the thread object to start the thread.
  • The example code is as follows.
public class ThreadTest {
    public static void main(String[] args){
        new TestThread().start();
    }
}

class TestThread extends Thread{
    @Override
    public void run(){
        System.out.println("Test Thread");
    }
}

Implement the Runnable interface:

  • Create a class to implement the Runnable interface and override the run method, the thread executor.
  • Create a class instance that implements the Runnable interface, and then pass the instance object as a target to the constructor of the Thread class, where the Thread class created is the thread object.
  • Invoke the start() method of the thread object to start the thread.
  • The example code is as follows.
public class ThreadTest {
    public static void main(String[] args){
        new Thread(new TestRunnable()).start();
    }
}

class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("Test Runnable");
    }
}

Implement the Callable class:

  • Create a class to implement the Callable interface and override the call method, which is the thread execution body. The call method has a return value.
  • Create a class instance that implements the Callable interface and pass the instance object as the constructor of the callable into the FutureTask class. The FutureTask object encapsulates the return value of the call() method of the Callable object and can be obtained using the get method.
  • The FutureTask instance object is passed into the Thread constructor as a target, and the Thread class created at this time is the Thread object.
  • Invoke the start() method of the thread object to start the thread.
  • The example code is as follows.
public class ThreadTest {
    public static void main(String[] args){
        FutureTask<Integer> task = new FutureTask(new TestCallable());
        new Thread(task).start();
        try {
            System.out.println(task.get());//get The method will get call Value after execution
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class TestCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int num = 0;
        while(num < 5){
            num++;
        }
        return num;
    }
}

6. sleep() and wait()

sleep():

  • The sleep() method is a static method of the Thread class of the thread class. If called in synchronized, the sleep() method will not release the lock.

wait():

  • The wait() method is a method of the thread class Object class. If called in synchronized, the wait() method releases the lock.
  • The example code is as follows (from the timestamp output from the console, it can be seen that sleep does not release the lock, it is blocked, and wait releases the lock):
public class ThreadTest {
    protected static volatile Object lock = "lock";

    public static void main(String[] args){

        for(int i=0;i<5;i++){
            new Thread(() -> {
                synchronized(lock){
                    System.out.println(Thread.currentThread().getName() + ": Waiting to start the current timestamp:" + System.currentTimeMillis());
                    try {
//                        lock.wait(1000);//Enter the current thread into the waiting pool
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

7. Concerning wait() and notify (), notify All ()

notify(),notifyAll():

  • notify() refers to randomly waking up a wait thread to compete for a lock in the object lock pool, while notify all () wakes up all wait threads to compete for a lock in the object lock pool.
  • The sample code for wait() and notify() methods is as follows:
public class ThreadTest {
    protected static volatile Object lock = "lock";

    public static void main(String[] args){
        new Thread(new TestRunnable(lock)).start();
        try {
            Thread.sleep(1000);
            System.out.println("sleep 1000ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new TestThread(lock).start();

    }
}

class TestRunnable implements Runnable{
    private Object lock;
    public TestRunnable(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock){
                System.out.println("begin wait: " + System.currentTimeMillis());
                System.out.println("Release lock........");
                lock.wait();//Enter the current thread into the waiting pool
                System.out.println("end wait: " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class TestThread extends Thread{
    private Object lock;
    public TestThread(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock){
            System.out.println("begin notify: " + System.currentTimeMillis());
            System.out.println("do something........");
            lock.notify();//Wake up one at random from the waiting pool wait Threads enter the lock pool to compete for locks
//            lock.notifyAll();//Wake up all at random from the waiting pool wait Threads enter the lock pool to compete for locks
            System.out.println("end notify: " + System.currentTimeMillis());
        }
    }
}

8. start() and run()

start():

  • This method is used to start a thread, after executing the start() method, the thread will enter the ready state, and then automatically execute the content of the run function without waiting, which is the true realization of multi-threading.

run():

  • Running this method separately, only using it as a common function, each execution must wait for the content of the run function to complete, then can it be executed down, unable to achieve multi-threading.

9. Thread security

It is mainly reflected in the following three aspects:

  • Atomicity: Only one thread is allowed to operate on the data at the same time (atomic class, synchronized, Lock at the beginning of atomic).
  • Visibility: A thread's modifications to shared variables can be observed by other threads in time (synchronized,volatile, Lock).
  • Orderliness: It refers to the fact that the order of execution of instructions in other threads can be observed, which is usually out of order due to instruction rearrangement (the happens-before principle).

10. Thread deadlock

Deadlock:

  • It refers to the phenomenon that multiple threads get stuck in loop congestion because of competing for resources in the execution process.
  • The sample code is as follows (after running, two threads are deadlocked and will remain locked if no external interruption occurs):
public class ThreadTest {
    //shared resource A
    private static Object A = new Object();
    //shared resource B
    private static Object B = new Object();

    public static void main(String[] args){
        new Thread(() -> {
            synchronized (A){
                System.out.println(Thread.currentThread().getName() + ": Get resources---A");
                try {
                    Thread.sleep(1000);//Hibernation here is to give execution opportunities to other threads
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": To get resources---B");
                synchronized (B){
                    System.out.println(Thread.currentThread().getName() + ": Get resources---B");
                }
            }
        },"Thread-01").start();

        new Thread(() -> {
            synchronized (B){
                System.out.println(Thread.currentThread().getName() + ": Get resources---B");
                try {
                    Thread.sleep(1000);//Hibernation here is to give execution opportunities to other threads
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": To get resources---A");
                synchronized (A){
                    System.out.println(Thread.currentThread().getName() + ": Get resources---A");
                }
            }
        },"Thread-02").start();
    }
}

Four necessary conditions of deadlock are how to avoid deadlock:

  • Mutual exclusion condition: a resource can only be occupied by one thread at any time. If other threads request the resource, they need to wait for the original thread to release the resource (this condition can not be broken, because the lock itself is for mutually exclusive access).
  • Request and retention conditions: a thread has acquired part of the resources, but also to request other resources, if other resources are occupied, the original thread is blocked, and will not release the resources it occupies (one-time application for all resources; or when blocked, release the resources it occupies).
  • Inalienable Conditions: A thread can not be deprived of its resources by other threads before it is used up, but can only be released after its own use (if it is blocked by resources being used by other threads, it can release the resources it occupies).
  • Loop waiting conditions: A head-to-tail circular waiting resource relationship (resource acquisition in a certain order) is formed between multiple processes.
  • For the above example, thread 1 can get resource AB first, then thread 2 can get resource AB after execution. The change is as follows: thread 1 first gets resource A, then releases CPU, thread 2 is ready to get resource A, and finds that resource A has been occupied. Thread 2 is blocked, then releases CPU after one second, thread 1 then executes to get resource B. Thread 2 begins to acquire resource A, because thread 1 has executed and released the lock, thread 2 normally acquires resource A, then hibernates to release CPU for one second, and then goes to acquire resource B, which is also acquired and executed normally:
public class ThreadTest {
//Shared resources A
private static Object A = new Object();
//Shared resources B
private static Object B = new Object();

public static void main(String[] args){
new Thread(() -> {
synchronized (A){
System.out.println(Thread.currentThread().getName() + ": Get resources---A");
try {
Thread.sleep(1000);//Hibernation here is to give execution opportunities to other threads
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": To get resources---B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + ": Get resources---B");
}
}
},"Thread-01").start();

new Thread(() -> {
//Thread 2 is blocked when it goes to get A
synchronized (A){
System.out.println(Thread.currentThread().getName() + ": Get resources---A");
try {
Thread.sleep(1000);//Hibernation here is to give execution opportunities to other threads
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": To get resources---B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + ": Get resources---B");
}
}
},"Thread-02").start();
}
}

All the above are personal notes. If there are any mistakes, please correct them.

Topics: Java jvm