Silicon Valley Video Summary - Java Multithreading

Keywords: Java Multithreading JavaSE

Multithreaded

First: Basic concepts: program, process, thread

Program: A program is a collection of instructions written in a language for a specific task. That is, a static piece of code, a static object.

Process: A process is an execution process of a program, or a running program, and is a dynamic process: it has its own process of generation, existence, and extinction. - Life cycle. As a unit of resource allocation, the system allocates different memory areas for each process at run time.

Threads: Processes can be further subdivided into threads, which are a path for execution within a program.

  • If a program executes multiple threads in parallel at the same time, it is said to support multiple threads
  • Threads are scheduled and executed, and each thread has its own run stack and program counters (PCs)
  • Multiple threads in a process share the same memory unit/memory address space - the heap and method area are owned by a process in which threads can access objects and variables. This makes communication between threads easier and more efficient, but system resources shared by multiple threads can pose security risks.

Advantages of multithreaded programs:

  1. Increase the responsiveness of your application. More meaningful for graphical interfaces and enhanced user experience.
  2. Improve CPU utilization of computer systems.
  3. Improve the program structure, divide long and complex processes into multiple threads, run independently, and facilitate understanding and modification.

When multithreading is required:

  1. A program needs to perform two or more tasks simultaneously
  2. When a program needs to perform some waiting tasks, such as user input, file read and write operations, network operations, etc.
  3. Some programs need to be run in the background

Multithreaded Creation

1.Inherited from Thread class

  1. Create a class that inherits from the Thread class
  2. Overrides the run() method of the Thread class, which contains the logic the thread needs to execute
  3. Object of a subclass of the new Thread class
  4. Start a thread by calling the start() method of the object
class ChildThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println("white");
            }
        }
    }
}
public class MyThread {

    public static void main(String[] args) {
        ChildThread t1 = new ChildThread();
        
        // The start() method has two functions, starting a thread and calling its run method
        // If t1.run() is called, no child threads are started, only the run() method of the object is called.
        // A thread object can only start() once
        
        t1.start();
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0) {
                System.out.println("dream" + "Main");
            }
        }
    }
}

Common methods in the Thread class:

  • start(): Starts the current thread and calls the thread's run() method
  • run(): Typically, you need to override the run() method in the Thread class, where you write the execution logic of the created thread
  • currentThread: Static method to get the currently executing thread
  • getName: Returns the name of the currently executing thread
  • setName: Sets the name of the current thread
  • yield(): Releases the execution power of the current CPU
  • join(): Executes the join() method of thread b in thread a, where thread a enters a blocking state, and thread a does not end the blocking state until thread b has finished executing
  • sleep(Long millitime): Sleep (block) the current thread for millitime milliseconds
  • isAlive: Determines if the current thread is alive

Thread priority:

  1. priority

    • MIN_PRIORITY = 1
      
    • NORM_PRIORITY = 5
      
    • MAX_PRIORITY = 10
      
  2. Get and set priorities

    public final int getPriority() {
            return priority;
        }
    
    public final void setPriority(int newPriority)
    

High-priority threads can grab resources from low-priority threads, but it's not absolute, it's just that they have this capability.

Ticket-selling instances (with potential hazards):

// Sharing resources among multiple threads poses certain security risks that can be resolved later

class WindowDemo extends Thread{

    // ticket variables need to be declared static, and multiple windows (objects) need to share a variable
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                System.out.println(getName() + ":" + "Sell ticket" + ticket);
                ticket--;
            }
            else{
                break;
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        WindowDemo w1 = new WindowDemo();
        WindowDemo w2 = new WindowDemo();
        WindowDemo w3 = new WindowDemo();

        w1.setName("Window 1");
        w2.setName("Window 2");
        w3.setName("Window 3");

        w1.start();
        w2.start();
        w3.start();
    }
}

2. Classes that implement the Runnable interface

  1. Create a class that implements the Runnable interface
  2. Implement methods in the Runnable interface (run() method)
  3. Create an object of this class
  4. Pass this object as a parameter into the constructor of the Thread class to create a Thread object
  5. Open threads by calling the start method of the Thread object
// Similarly, this class has thread security issues
class WindowDemo2 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                // This class is not a subclass of the Thread class, so you need to call the full getName() method
                System.out.println(Thread.currentThread().getName() + ":" + "Sell ticket" + ticket);
                ticket--;
            }
            else{
                break;
            }
        }
    }
}
public class SellTicketDemo2 {

    public static void main(String[] args) {
        WindowDemo2 windowDemo2 = new WindowDemo2();
        
        // You don't need to declare ticket static here, because it's a thread created with the same object, and three threads access the same ticket
        Thread t1 = new Thread(windowDemo2);
        t1.setName("Window 1");
        Thread t2 = new Thread(windowDemo2);
        t2.setName("Window 2");
        Thread t3 = new Thread(windowDemo2);
        t3.setName("Window 3");
        
        /*The start method of the Thread class is called, and the start method calls run() of the Thread class. Why is this where he calls the implementation?
         *Runnable What about the run() method of the interface class, because the start() method of the Thread class first determines if the Runnable interface is passed in
         *Class, if so, calls the run() method that implements the Runnable interface class 
         */
        
        t1.start();
        t2.start();
        t3.start();
    }
}

Compare the two ways to create multithreads:

  1. In development, the second option is preferred, which implements the Runnable interface
    1. Implementing interfaces frees you from the limitations of class inheritance in Java
    2. When multiple threads need to be opened and they have shared data, it is more appropriate to implement the interface
  2. In fact, the Thread class is the class that implements the Runnable interface. We create a class that inherits the Thread class and overrides its run() method, ultimately implementing the run() method in the Runnable interface.

3.Two new approaches to JDK5.0

3.1 Class implementing Callable interface

Callable is more powerful than Runnable

  • The call method may have a return value
  • Method can throw an exception
  • Return values supporting generics
  • You need to use the FutureTask class, for example, to get the returned results

Steps to achieve:

  1. Create an implementation class that implements the Callable interface
  2. Implement a class override call method that writes the logic this thread needs to execute in the call() method
  3. Create an object that implements a class
  4. Create a FutureTask object by passing the object that implements the class as a parameter into the constructor of FutureTask
  5. Pass the FutureTask object as a parameter into the constructor of the Thread class, create a Thread object, and call the start() method
class ThreadTest implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println("Traversed to" + i);
                sum += i;
            }
        }
        System.out.println("And for:" + sum);
        return sum;
    }
}
public class CallableDemo {
    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
		
        // The only implementation class for the Future interface that implements the Runnable and Callable interfaces
        FutureTask<Integer> integerFutureTask = new FutureTask<>(threadTest);

        new Thread(integerFutureTask).start();

        try {
            Integer ans = integerFutureTask.get();
            System.out.println(ans);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

3.2 Use Thread Pool

Idea: Create many threads in advance into the thread pool, get them directly when they are used, and put them back into the pool when they are finished. This prevents threads from being destroyed multiple times, thereby achieving reuse.

Benefits:

  • Increase response speed (reduce time to create new threads)

  • Reduce resource consumption (reuse threads in the thread pool, do not need to be created every time)

  • Easy Thread Management

    • corePoolSize: Core pool size
    • maximumPoolSize: Maximum number of threads
    • KeepAliveTime: Maximum amount of time a thread will terminate when it has no tasks

Thread life cycle

The Thread.State class defines five states in the thread life cycle:

  • When a Thread class or its subclass object is life and created, the new thread object is in the newly created state (New)
  • When a thread in the new state invokes the start method, it enters the thread queue and waits for the CPU time slice, when it is ready to run, but it has not been allocated memory resources by the CPU (ready)
  • When a ready thread is scheduled and CPU resources are available, it enters a running state, and the run() method defines the operations and functions of the thread (run)
  • In a special case, when the input and output are artificially suspended or executed, the CPU is released and its execution is temporarily terminated, entering a blocking state (blocking)
  • Thread completes all its functions or is forced to terminate or an exception causes the thread to end (die)

Multithreaded security issues:

  1. Uncertainty in execution of multiple threads causes unstable execution results
  2. Sharing of data by multiple threads, potentially corrupting the data

Solution

Solution: When one thread is operating on shared data, other threads cannot participate until the end of the thread, allowing other threads to operate on shared data.

In Java, we solve thread security issues through synchronization mechanisms.

**Method 1: ** Synchronize Code Blocks

Synchronized {

//Code that needs to be synchronized, (code that operates on shared data), shared data is a variable that multiple threads operate on together.

}

Synchronization monitors are commonly referred to as "locks", and any object can be used. However, all threads with potential security risks need to share an object, that is, they share a lock.

**Method 2: **Synchronization Method

If the synchronization code block is fully declared in a method, we can declare the method synchronous.

The synchronized method is declared by prefixing the method's return value with the synchronized keyword.

Synchronization methods still use synchronization monitors, but they don't need to be proactively declared.

Non-static synchronization method, synchronization monitor is this. This corresponds to creating a thread as a Runnable interface

Static synchronization method where the synchronization monitor is the current class itself. This corresponds to creating a thread as a subclass of the Thread class

Synchronization solves the thread security issue, but only one thread at a time can execute the synchronization code block, which is equivalent to reverting to a single-threaded process (limitation).

Lazy thread security in single-case mode

1. What is the singleton pattern

The singleton mode ensures that only one instance of a specified class can be available at any time throughout the application cycle.

How to implement the singleton pattern:

  1. Construct method declared private, outside cannot call construct method to construct object
  2. The class itself needs to construct an object -- just call the construction method
  3. Provide this unique instance object publicly

2.Example

// Only getInstance() method can be used by the outside world to obtain unique objects of the Bank class
class Bank{
    private Bank(){};
    
    private static Bank instance = null;

    public static Bank getInstance(){
        // This layer of if statements can increase efficiency, since the synchronization operation actually only needs to enter the first
        // Thread operation.
        if(instance == null){
            // Synchronize Code Blocks
            synchronized(Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

Thread Deadlock Problem

  • Understanding deadlocks
    • Different threads occupy the synchronization listeners they need and do not give up, waiting for the other thread to occupy the synchronization listeners they need, thus creating a thread deadlock.
    • When a deadlock occurs, the program does not throw an exception or prompt, but the thread is blocked and cannot continue. This does not fit our definition of a thread, and all threads should eventually die.
  • Solution
    • Specialized algorithms, principles
    • Minimize the definition of synchronization resources
    • Avoid nested synchronization whenever possible

Deadlock example:

public class DeadLock {
    public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append('a');
                    s2.append(1);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append('b');
                        s2.append(2);
                    }
                }
                System.out.println(s1);
                System.out.println(s2);
            }
        }.start();

        new Thread(() -> {
            synchronized (s2){
                s1.append('c');
                s2.append(3);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s1){
                    s1.append('d');
                    s2.append(4);
                }
            }
            System.out.println(s1);
            System.out.println(s2);
        }).start();
    }
}

Lock Lock

Lock lock is a new thread security solution for JDK5.0. Lock is also an interface that we use to implement the class ReentrantLock. //It is an abstract lock itself.

class WindowDemo extends Thread{

    // ticket variables need to be declared static, and multiple windows (objects) need to share a variable
	
    // Lock locks are also declared static
    private static ReentrantLock lock = new ReentrantLock(true);
    
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticket > 0) {
                    System.out.println(getName() + ":" + "Sell ticket" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
            finally {
                lock.unlock();
            }

        }
    }

The difference between synchronized and Lock:

  • The synchronized mechanism is to automatically release the synchronization monitor after executing the synchronization code block or method.

  • Lock needs to manually turn on synchronization (Lock.lock()) and end synchronization manually (Lock.unlock()) after the synchronization ends.

Thread Communication

Thread communication involves three methods:

  • wait(): Execute this method, the current thread will be blocked and the synchronization monitor will be released.
  • notify(): Executing this method will wake up a thread that is waiting () and the thread with the highest priority if more than one thread is waiting ().
  • notifyAll(): Executing this method will wake up the thread that is therefore waiting ().

Be careful:

  • The above three methods must be used in a synchronization block or synchronization method
  • Callers of the above three methods must be synchronization listeners for synchronization blocks or methods
  • The three methods above are defined in the java.lang.Object class

Differences between sleep() and wait() methods:

Same point: Once the above method is executed, the thread currently in progress will become blocked.

Differences: 1) The sleep() method is declared static in the Thread class, and the wait() method is declared in the Object() class.

2) The call requirements are different, as long as we want, we can always call the sleep () method through Thread.sleep(), while the wait() method must be called in the synchronization method or in the synchronization code block.

3) Calling the wait() method releases the synchronization monitor, while calling the sleep() method does not release the synchronization monitor.

producer consumer problem

There is a producer who can always produce products, and counter workers can get products from producers, and consumers can buy products from counter workers. A maximum of 66 albums are required. Once 66 products are available, counter workers will notify producers not to produce them, and counter workers will tell consumers not to do so when there is no product available.Come shopping, no more.

Analysis:

  • Multi-threaded problem, producer is a kind of thread, consumer is a kind of thread.
  • Thread security issues exist, and there is shared data between them, products in the hands of desk workers or desk workers
/*
 * Clerk The class represents the counter worker, and the member variable productNumber represents the number of current products.
 * A producer can call the produce() method to produce a product
 * Consumers can call the consume() method to indicate that they have purchased a product
 * Producers and consumers need to call the getProductNumber() method to get the current number of products
 */
class Clerk{
    private int productNumber = 0;
    public int getProductNumber() {
        return productNumber;
    }

    public void produce() {
        productNumber++;
    }

    public void consume() {
        productNumber--;
    }
}
class Producer implements Runnable{
    // Declare the member variable clerk, which is a multithreaded common variable used as a synchronous lock

    private Clerk clerk;

    // Declare construction methods from outside to public clerk

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (clerk) {
                if (clerk.getProductNumber() < 66) {
                    // The notify method and the wait method should call the same object as the synchronous lock. If you do not declare who called the method, the default is this.
                    clerk.notify();
                    clerk.produce();
                    System.out.println(Thread.currentThread().getName() + "Produced products:" + clerk.getProductNumber());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        clerk.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
class Customer implements Runnable{

    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (clerk) {
                if (clerk.getProductNumber() > 0) {
                    // The notify method and the wait method should call the same object as the synchronous lock. If you do not declare who called the method, the default is this.
                    clerk.notify();
                    System.out.println(Thread.currentThread().getName() + "Consumer Products:" + clerk.getProductNumber());
                    clerk.consume();
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    try {
                        clerk.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Customer customer = new Customer(clerk);
        Customer customer1 = new Customer(clerk);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(customer);
        Thread t3 = new Thread(customer1);
        t1.setName("Producer 1");
        t2.setName("Consumer 1");
        t3.setName("Consumer 2");
        t1.start();
        t2.start();
        t3.start();
    }
}

Posted by Xajel on Sun, 10 Oct 2021 10:36:22 -0700