multithreading

8 minute read

Published:

Java basics – multithreading

Multithreading

basic concepts

  1. Concurrency and Parallelism

    • Concurrency: multiple events occur in the same time period (alternate execution)
    • Parallel: multiple events happen at the same time (simultaneous execution)
  2. Processes and threads

    • Process: an application running in memory
    • Thread: an execution unit in the process, responsible for the execution of the program in the current process
  3. Thread scheduling

    • Time-sharing scheduling: All threads use the CPU in turn, and the time occupied by each thread is evenly distributed
    • Preemptive scheduling: give priority to threads with high priority to use CPU, if the priority is the same, randomly select one
  4. Main thread: the thread that executes the main method

    Single-threaded program: There is only one thread in the program

Multithreading in Java

java.lang.Thread is a class that describes threads in Java

  • Implementation steps:
  1. Create a subclass of Thread
  2. Override the run method in the Thread class in a subclass of the Thread class to set thread tasks
  3. Create a subclass object of the Thread class
  4. Call the start method in the Thread class, start a new thread, and execute the run method
  • Note: It is illegal to start a thread multiple times, especially after the current thread has been executed, it cannot be restarted

  • Common methods
    1. String getName(): returns the name of the thread
    2. static Thread currentThread(): Returns the reference of the currently executing thread
    3. void SetName(String name): change the thread name
    4. Thread(String name): Constructor, allocate a new thread object
    5. static void sleep(long millis): make the currently executing thread pause at the specified millisecond value
  • Create thread method two

    Implement Runnable interface

    1. Create a Runnable interface implementation class
    2. Override the run method in the implementation class and set thread tasks
    3. Create an implementation class object
    4. Create a Thread implementation class object, and pass the implementation class object of the Runnable interface in the constructor
    5. Call the start method in the Thread class to start a new thread and execute the run method
  • Realize the benefits of the Runnable interface to create multiple threads
    1. Avoid the limitations of single inheritance
    2. Enhance the scalability of the program and reduce the coupling of the program (set thread tasks and start threads)
  • Anonymous inner class implements thread creation

    1. Thread method

       new Thread(){
           @Override
           public void run(){
               //...
           }
       }.start();
      
    2. Runnable way

       new Thread(new Runnable(){
           @Override
           public void run(){
               //...
           }
       }).start();
      

Thread safety

Multi-threaded access to shared data will cause thread safety issues

Thread synchronization

Thread safety issues can be solved through thread synchronization technology

  1. Synchronize code blocks
  2. Synchronization method
  3. Locking mechanism

Sync code block

The synchronized keyword can be used in a block in the method, which means that only mutually exclusive access to the resources of this block is performed

synchronized (synchronization lock) {
    Code that needs to be synchronized
}

Sync method

Synchronous methods ensure that only one thread can execute the method at the same time

public synchronized void method(){
    Code that needs to be synchronized
}

The lock object of the synchronization method is this

Note: static synchronization method

public static synchronized void method(){
    Code that needs to be synchronized
}

The lock object of the static synchronization method is the class attribute of this class -> class file object

Lock

java.util.concurrent.Locks.Lock interface

Lock implementation provides a wider range of locking operations than using synchronized methods and statements

  • Methods in the Lock interface
    1. void lock(): Acquire a lock
    2. void unlock(): release the lock

java.util.concurrent.Locks.ReentrantLock implements the Lock interface

Steps for usage:

  1. Create a ReentrantLock object in the member location
  2. Call lock to obtain the lock before the code that may have thread safety issues
  3. Call unlock to release the lock after the code that may have thread safety issues

Thread status

  1. Time WAITING
  2. BLOCKED
  3. WAITING
  4. NEW
  5. BLOCKED
  6. RUNNABLE
  7. TERMINATED
  • wait(): The current thread is waiting
  • notify(): wake up a single thread on the object monitor
  • wait(long m): If the wait method is not awakened by notify after the end of the millisecond value, it will automatically wake up and the thread will enter the Runnable/Block state
  • notifyAll(): wake up all threads on the object monitor

Waiting for the wake-up mechanism

Inter-thread communication

Concept: multiple threads are processing the same resource, but the processing actions are different

When multiple threads operate on the same data, it is necessary to avoid contention for the same shared variable

Wait and wake up mechanism

  • Method:
  1. wait(): The thread is no longer active and enters the wait set without wasting CPU resources. At this time, the thread status is WAITING.
  2. notify(): select a thread in the wait set of the notified object to release
  3. notifyAll(): select all threads in the wait set of the notified object to release
  • Note:

    1. Even if only one waiting thread is notified, the notified thread cannot resume execution immediately (need to try again to acquire the lock)
    2. If the lock can be obtained, the thread changes from the WAITING state to the RUNNABLE state
    3. Otherwise, come out of the wait set and enter the entry set again, and the thread changes from the WAITING state to the BLOCKED state
    4. The wait and notify methods must be caled by the same lock object
    5. The wait and notify methods are methods belonging to the Object class, and objects of any class can be used
    6. The wait and notify methods must be used in synchronous code blocks or synchronous functions

Producer and consumer issues

The wait and wake mechanism deals with the issue of producer and consumer

We just make a buffer by ourselves, and the communication between the producer and the consumer passes through this buffer. A value of “” indicates that the buffer is empty, and a value other than “” indicates that the buffer is full:

public class ValueObject
{
    public static String value = "";
}

Next is a producer, if the buffer is full, then wait(), no longer produce, and wait for the consumer to be notified when the consumer is finished; if the buffer is empty, then produce the data to the buffer

public class Producer
{
    private Object lock;
    
    public Producer(Object lock)
    {
        this.lock = lock;
    }
    
    public void setValue()
    {
        try
        {
            synchronized (lock)
            {
                if (!ValueObject.value.equals(""))
                    lock.wait();
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                System.out.println("The value of Set is:" + value);
                ValueObject.value = value;
                lock.notify();
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

Consumers are similar, if the buffer is empty, then no more consumption, wait() waits, waiting for the producer to produce the notification; if the buffer is not empty, then go get the data:

public class Customer
{
    private Object lock;
    
    public Customer(Object lock)
    {
        this.lock = lock;
    }
    
    public void getValue()
    {
        try
        {
            synchronized (lock)
            {
                if (ValueObject.value.equals(""))
                    lock.wait();
                System.out.println("The value of Get is:" + ValueObject.value);
                ValueObject.value = "";
                lock.notify();
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

Write a main function, open two threads to call the getValue() method in Producer and the setValue() method in Customer():

public static void main(String[] args)
{
    Object lock = new Object();
    final Producer producer = new Producer(lock);
    final Customer customer = new Customer(lock);
    Runnable producerRunnable = new Runnable()
    {
        public void run()
        {
            while (true)
            {
                producer.setValue();
            }
        }
    };
    Runnable customerRunnable = new Runnable()
    {
        public void run()
        {
            while (true)
            {
                customer.getValue();
            }
        }
    };
    Thread producerThread = new Thread(producerRunnable);
    Thread CustomerThread = new Thread(customerRunnable);
    producerThread.start();
    CustomerThread.start();
}

Thread Pool

Principle

Thread pool -> collection (LinkedList<Thread>)

  1. When the program is started for the first time, create multiple threads and save them in a collection

  2. When you want to use a thread, you can remove the thread from the collection for use

  3. After use, plan the thread to the thread pool

After JDK1.5+, a thread pool is built-in and can be used directly

Use

  • java.util.concurrent.Executors thread pool factory class, there are static methods to create thread pool

    static ExecutorService newFixedThreadPool(int nThread): Create a reusable fixed thread thread pool

  • java.util.concurrent.ExecutorService thread pool interface

  1. submit(Runnable task): Submit a Runnable task for execution, obtain threads from the thread pool, and execute thread tasks
  2. shutdown(): Close and destroy thread pool To
  • Use of thread pool

    1. Use the static method newFixedThreadPool in the factory class of the Executors thread pool to produce a thread pool with a specified number of threads
    2. Create a class, implement the Runnable interface, rewrite the run method, and set thread tasks
    3. Call the submit method in ExecutorService, start the thread, and execute the run method
    4. Call the shutdown method in ExecutorService to destroy the thread pool (not recommended)