Java learning notes

Related concepts

process

A process is an execution process of a program, or a program being executed. It is a dynamic process with its own process of emergence, existence and extinction

thread

A thread is created by a process. It is an entity of a process. A process can have multiple threads

Single thread

Only one thread is allowed to execute at a time

Multithreading

Multiple threads can be executed at the same time, such as qq opening multiple chat windows

Concurrent

At the same time, multiple tasks are executed alternately. For example, a single core cpu implements multiple tasks

parallel

At the same time, multiple tasks are executed at the same time. For example, multiple tasks are realized by multi-core cpu

Thread usage

Two ways to use threads

  • Inherit Thread
  • Implement Runnable

Inherit Thread usage

When a class inherits the Thread class, it can be used as a Thread, but it needs to rewrite the run method to write its own business logic

The Thread class is a run method that implements the Runnable interface. Source code:

	  @Override
      public void run() {
          if (target != null) {
              target.run();
          }
      }

Use steps:

  1. Implement a class that inherits Thread
  2. Override the run() method
  3. Create a new object of this class in the main method and call the start() method

For example:

public class MeowThread {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        cat.start();    // Start the thread Thread-0 and eventually call the run method
        // When the main thread, that is, the main method starts the sub thread thread Thread-0, the main thread will not block
        // That is, it will not wait for Thread-0 to execute
        // At this point, the main thread and child threads execute alternately
        for (int i = 0; i < 1000; i++) {
            System.out.println("Main thread i= " + i + "´╝îThread Name:" + Thread.currentThread().getName());
            // Main thread sleep 0.3s
            Thread.sleep(300);
        }
    }
}

/**
 * When a class inherits the Thread class, it can be used as a Thread
 * You need to rewrite the run method and write your own business logic in it
 * Thread Class is a run method that implements the Runnable interface
 *     @Override
 *     public void run() {
 *         if (target != null) {
 *             target.run();
 *         }
 *     }
 */
class Cat extends Thread {
    int times = 0;  // Record the number of thread runs
    @Override
    public void run() { // Rewrite the run method and write your own business logic
        while (true) {
            // Output every 0.3 seconds
            System.out.println("Meow, meow, I'm a kitten " + (++times) + "Time, thread Name:" + Thread.currentThread().getName());
            // Let the process sleep for one second
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 1000) {  // Exit after 20 runs
                break;
            }
        }
    }
}

You can see:

  • Main thread and sub thread execute alternately
  • When running, you can enter Jconsole on the console, select the process corresponding to the code, and then click the process to view the resource usage
  • Instead of calling the run() method directly, the start() method is used in the middle, because if it is the latter, the main method calls the run() method, which will not start the sub thread, and will become a serialized execution, that is, the code after the run() method is executed

Source code execution process of start() method:

  • After calling start(), you will enter another method start0()
  • start0() is a local method, which is called by the JVM, and the bottom layer is implemented by c/c + +. It is a real way to implement multithreading
  • After the start() method calls start0(), the thread does not necessarily execute immediately, but becomes ready. When to execute depends on when the CPU is scheduled

Implement Runnable interface

  • Java is a single inheritance mechanism. If a class has inherited a class, it cannot inherit the Thread class. You can only create threads by implementing the Runnable interface

Use steps:

  1. Create a class that implements the Runnable interface
  2. Implement the run() method and write your own functions in it
  3. Create a new object obj of this class in the main method
  4. Then create a new Thread class object in the main method, and pass in the object obj created in the previous step when creating
  5. Call the start() method of the object of the Thread class

The proxy pattern in the design pattern is used here. In short, the object obj of a class that implements the Runnable interface is passed into the object thread of the thread class. When the latter object thread calls the start() method, the final called run() method will call the run() method of obj through the dynamic binding mechanism

example:

public class ThreadTest02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // dog.start();  This method cannot be used because it does not exist in the Dog class
        // So you can create a thread object to put the dog object (the object that implements the Runnable class) into it
        // Here, the underlying design pattern -- agent pattern is used
        Thread thread = new Thread(dog);
        thread.start();
    }
}

/**
 * Create a thread by implementing the Runnable interface
 */
class Dog implements Runnable {

    int count = 0;  // count
    @Override
    public void run() {
        while (true) {
            System.out.println("Dog barking" + (++count) + "Time, thread Name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

Moreover, Runnable can share resources with multiple threads:

public class ThreadTest04 {
    public static void main(String[] args) {
        Resource resource = new Resource();
        Thread thread = new Thread(resource);
        Thread thread1 = new Thread(resource);
        // Two threads use one object and consume the same resource
        thread.start();
        thread1.start();
    }
}

class Resource implements Runnable {
    int resource = 1000;
    @Override
    public void run() {
        while (resource > 0) {
            System.out.println("Process:" + Thread.currentThread().getName() + " Consumed resources" + (--resource));
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Comparison of the two methods

  • From the perspective of Java design, the two methods are essentially the same
  • The implementation of Runnable interface is more suitable for multiple threads sharing a resource, and avoids the limitation of single inheritance

Thread termination

  • Automatic termination after execution
  • Notification mode: use variables to control the exit mode of run

example:

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        ExitThread exitThread = new ExitThread();
        exitThread.start();

        // If you want to control the end of the thread in the main method, you can modify the loop variable
        Thread.sleep(5000);
        exitThread.setLoop(false);
    }
}

class ExitThread extends Thread {
    private int count = 0;
    private boolean loop = true;

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("running......");
        }
    }
}

Common thread methods

Group 1:

  • start()
  • run()
  • getName()
  • setName()
  • setPriority() set priority
  • getPriority() change priority
  • sleep()
  • interrupt() interrupt thread

example:

public class ThreadInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Interrupted interrupted = new Interrupted();
        interrupted.setName("Yasuo");
        interrupted.setPriority(Thread.MIN_PRIORITY);
        interrupted.start();

        // After the main thread sleeps for 2 seconds, interrupt the child thread
        Thread.sleep(2000);
        interrupted.interrupt();
    }
}

class Interrupted extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i + " times is running");
        }
        try {
            System.out.println(Thread.currentThread().getName() + "Dormant");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // Interrupted in sleep
            System.out.println(Thread.currentThread().getName() + "Interrupted");
        }
    }
}

Group 2:

  • yield: cede the CPU occupied by the thread, but the ceding may not be successful because the ceding time is uncertain. For example, when CPU resources are large enough for two threads, there will be no comity
  • join: thread queue jumping. Once the insertion is successful, all tasks of the inserted thread will be executed first. For example, if two threads t1 and t2 call the method t2.join() during t1 execution, the cpu will execute t2 and will not continue to execute t1 until t2 is completed

example:

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        JoinThread joinThread = new JoinThread();
        joinThread.start();

        for (int i = 0; i < 20; i++) {
            Thread.sleep(300);
            System.out.println("Main thread " + Thread.currentThread().getName() + " Play games " + (i + 1));
            // After the main thread executes 5 times, the child threads are queued
            if (i == 4) {
                System.out.println("The main thread has played enough. Give it to the child thread");
                // Starting from this point, the child thread will not continue to execute the main thread until it has finished executing
                joinThread.join();
            }
        }
    }
}

class JoinThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Child thread " + Thread.currentThread().getName() + " Play games " + (i + 1));
        }
    }
}

User thread and daemon thread

  • User thread: also called worker thread. Generally, the task is completed or terminated in the form of notification
  • Daemon thread: it serves the worker thread. When all user threads end, the daemon thread ends automatically
  • Common daemon threads: garbage collection mechanism

You can set a thread as a daemon through the method setDaemon()

example:

public class ThreadDaemon {
    public static void main(String[] args) throws InterruptedException {
        DaemonThread daemonThread = new DaemonThread();
        // If you want the main thread to end, the child thread will end automatically
        // You need to set the child thread as a daemon thread
        daemonThread.setDaemon(true);
        daemonThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("The car passed on the road~~~~~~~~~~");
            Thread.sleep(500);
        }
    }
}

class DaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Jerry's dog is running in the field~~~~~~~~~");
        }
    }
}

Java thread life cycle

  • NEW: the thread created but not started is in this state
  • RUNNABLE: the executing thread is in this state, including ready and Running. Ready is ready and Running is Running. For example, a thread in the Running state can enter the ready state by calling the yield() party or using the time slice completion method, and the ready state to the Running state depends on the scheduling method of the operating system kernel
  • BLOCKED: the BLOCKED waiting thread is in this state
  • WAITING: a thread that is WAITING for another thread to perform a specific action is in this state. For example, a thread will enter this state if it calls the join() method
  • TIMED_ Waiting: a thread that is waiting for another thread to perform a specific action for a specified waiting time is in this state. For example, a thread will enter this state if it calls the sleep() method
  • TEMINATED: the exited thread is in this state

You can get the thread state through the method getState()

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        State state = new State();
        // Status after new and before start
        System.out.println(state.getName() + " state " + state.getState());    // NEW
        state.start();
        // Status after start
        while (Thread.State.TERMINATED != state.getState()) {
            System.out.println(state.getName() + " state " + state.getState());    // TIMED_WAITING / RUNNABLE
            Thread.sleep(300);
        }
        // Status after abort
        System.out.println(state.getName() + " state " + state.getState());    // TERMINATED
    }
}

class State extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            break;
        }
    }
}

Thread synchronization

  • In a multithreaded scenario, some sensitive resources are not allowed to be accessed by multiple threads at the same time. At this time, thread synchronization is required to ensure that only one thread can access the data at any time
  • It can also be understood that after using thread synchronization, when one thread operates on the memory, other threads are not allowed to operate on the memory until the thread completes the operation

Method of thread synchronization

  1. Synchronous code block
synchronized (object) {	// Get the lock of the object to operate the synchronization code
	// Code to be synchronized
}
  1. Synchronized can also be placed in a method declaration to indicate that the entire method is a synchronized method
public synchronized void func(String s) {
	// Method body
}

example:

public class SellTicket {
    public static void main(String[] args) {

        SellTicker03 sellTicker03 = new SellTicker03();
        Thread thread = new Thread(sellTicker03);
        Thread thread2 = new Thread(sellTicker03);
        Thread thread3 = new Thread(sellTicker03);
        thread.start();
        thread2.start();
        thread3.start();
    }
}

class SellTicker03 implements Runnable {
    private int count = 100;    // Number of votes
    private boolean loop = true;    // Control thread end
    @Override
    public void run() {
        while (loop) {

            sell(); // Ticket selling method using thread synchronization
        }
    }
    // Ticketing method
    // Thread synchronization using synchronized
    public synchronized void sell() {   // Synchronization method. At the same time, only one thread can execute sell
        if (count <= 0) {
            System.out.println("End of ticket sales");
            loop = false;   // Thread stop loop
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread:" + Thread.currentThread().getName() + " Ticket sales, number of remaining tickets" + (--count));
    }
}

Thread synchronization principle

  • Thread synchronization is like adding a mutex lock to the synchronized data or memory. Whenever an object accesses the synchronized content, the synchronized part is locked and other threads cannot access it
  • synchronized can be added to the method. In this case, the lock is added to this, that is, the object calling the method. In the previous example, the three Thread objects all use a SellTicked03 object, so the shackles can be mutually exclusive
  • synchronized can also be added to code blocks
  • synchronized is loaded on the static method, which is equivalent to locking this class
  • The smaller the granularity of locking, the higher the performance

Locking method of code block:

synchronized(this) {	// The parenthesis here can be this or other objects (but it needs to be the same object)
	// Code block content 
}

For example, for the last example, change the locking method

class SellTicker03 implements Runnable {
    private int count = 100;    // Number of votes
    private boolean loop = true;    // Control thread end
	private Object obj = new Object();

    @Override
    public void run() {
        while (loop) {

            sell(); // Ticket selling method using thread synchronization
        }
    }
	// Ticketing method
    public  void sell() {   // Synchronization method. At the same time, only one thread can execute sell
        // Code block lock this
        synchronized(this) {
            if (count <= 0) {
                System.out.println("End of ticket sales");
                loop = false;   // Thread stop loop
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread:" + Thread.currentThread().getName() + " Ticket sales, number of remaining tickets" + (--count));
        }
        /*
        // The locking obj effect of code block is the same as above
        synchronized(obj) {
            if (count <= 0) {
                System.out.println("End of ticketing ");
                loop = false;   // Thread stop loop
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread: "+ Thread.currentThread().getName() +" ticket sales, number of remaining tickets "+ (-- count));
        }
        */
    }

Synchronized addition to static methods and use of synchronized code blocks in static methods:

	// Using the synchronized keyword for static methods is equivalent to locking this class
    public synchronized static void m1() {

    }
    // Implementing synchronous code blocks in static methods
    public static void m2() {
        synchronized (SellTicker03.class) { // The class name of this class is required in parentheses
            System.out.println("m22222222");
        }
    }

be careful;

  • If the synchronization method is not decorated with static, the default lock object is this
    -If the synchronization method is decorated with static, the default lock object is: current class

Steps to implement mutex:

  1. Analyze the code that needs to be locked
  2. Select a synchronization code block or synchronization method
  3. The lock objects of multiple threads should be the same

Thread deadlock

Deadlock is the blocking state caused by all threads competing for resources, which will make all threads unable to continue to execute

example:

public class ThreadDeadLock {
    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock(true);
        DeadLock deadLock1 = new DeadLock(false);
        deadLock.setName("A");
        deadLock1.setName("B");
        deadLock.start();
        deadLock1.start();
    }
}

class DeadLock extends Thread {
    boolean flag;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public DeadLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        
        if (flag) {
            synchronized (o1) { // Object mutex, the following is the synchronization code
                System.out.println(Thread.currentThread().getName() + " Enter 1");
                synchronized (o2) { // Object mutex, the following is the synchronization code
                    System.out.println(Thread.currentThread().getName() + " Enter 2");
                }
            }
        } else {
            synchronized (o2) { // Object mutex, the following is the synchronization code
                System.out.println(Thread.currentThread().getName() + " Enter 3");
                synchronized (o1) { // Object mutex, the following is the synchronization code
                    System.out.println(Thread.currentThread().getName() + " Enter 4");
                }
            }
        }
    }
}

In the above example, the program may enter a deadlock state after running:

  1. When thread A enters the run() method, it will get the lock of object o1 first. If thread B also enters the run() method before A gets the lock of o2, it will get the lock of o2 in advance.
  2. Then, A will wait for the o2 lock and B will wait for the o1 lock, entering an infinite waiting process
  3. This is a deadlock

Release lock

The following action will release the lock

  • The current thread synchronization method and synchronization code block have been executed. It's like playing a game
  • The current thread encounters break and return in the synchronization method and synchronization code block. It's like playing a game and being called to dinner
  • The current thread has an exception in the synchronization method and synchronization code block, resulting in the end of the program. It's like half of a game. The handle is broken
  • The current thread executes the wait() method of the thread object in the synchronization method and synchronization code block. The current thread pauses and releases the lock. It's like playing a game halfway. At this time, you need to queue up into a server and play again later

The following conditions do not release the lock:

  • The current thread calls the sleep(), yield() method in the synchronization method and the synchronous code block to suspend the execution of the current thread and does not release the lock. It's like playing a game and falling asleep, but the game is not offline
  • In the thread execution synchronization code block, other threads call the suspend() method of the thread to suspend the thread. It's like playing a game and suddenly dropping the line. I won't leave and will be ready to continue playing

Tags: Java Back-end

Posted on Thu, 28 Oct 2021 09:15:57 -0400 by paulsiew2