You will know why interviewers like to ask such questions!

Posted by Xianoth on Tue, 28 Dec 2021 02:01:04 +0100

You will know why interviewers like to ask such questions!

One day, Zhang San went to an interview and was asked a question by the interviewer:

What is multithreading?

Zhang San: multithreading means that multiple threads execute on the CPU, seize CPU resources and improve CPU execution efficiency


Interviewer: what are you talking about? What do you know? What did you learn? Go back, you!!!

Zhang San was very upset. When he came out, he directly robbed aunt's * * java from getting started to giving up * *, and reviewed java multithreading as soon as he went back.

1, Understanding multithreading

1. Multitasking
2. Multithreading
3. Program, process and thread
  • Program: an ordered collection of instructions and data. It has no running meaning. It is a static concept.
  • Process: it is an execution process of a program and a dynamic concept. It is the basic unit allocated by the system.
  • Thread: a process usually contains several threads, which is the basic unit of cpu scheduling and execution.

Multithreading (multithreading) refers to the technology of realizing concurrent execution of multiple threads from software or hardware. Computers with multithreading capability can execute more than one Thread at the same time due to hardware support, so as to improve the overall processing performance. Systems with this capability include symmetric multiprocessors, multi-core processors, chip level multiprocessing or simultaneous multithreading Manager. In a program, these independently running program segments are called "threads", and the concept of programming using them is called "multithreading".

2, Create thread

1. Inherit Thread class
  • The custom Thread class inherits the Thread class

  • Rewrite the run() method to write the thread execution body

  • Create a thread object and call the star() method to start the thread

    public class MyThread extends Thread{
        private int key = 100;
        public synchronized int getKey(){
            return key;
        }
        public synchronized void setKey(){
            this.key -= 1;
        }
        @Override
        public void run(){
            while(getKey()>0){
                System.out.println("MyThread");
                setKey();
            }
        }
        
        //Main thread
        public static void main(String []args){
            MyThread myThread = new Mythread();//Create thread
    
            myThread.start();//Start thread
            while(myThread.getKey()>0){
                System.out.println("Main thread");
                myThread.setKey();
            }
        }
    }
    
2. Implement Runnable interface
  • Define Runnable interface implementation class

  • Implement the run() method and write the thread execution body

  • Create a thread object and call the start() method to start the thread

  • It solves the limitation of single inheritance

    public class MyRunnable implements Runnable{
        private int key = 100;
        public synchronized int getKey(){
            return key;
        }
        public synchronized void setKey(){
            this.key -= 1;
        }
        @Override
        public void run(){
            while(getKey()>0){
                System.out.println("MyThread");
                setKey();
            }
        }
        //Main thread
        public static void main(String []args){
            MyRunnable myRunnable = new MyRunnable();
    		Thread myThread = new Thread(myRunnable);//Create thread
            myThread.start();//Start thread
            while(myRunnable.getKey()>0){
                System.out.println("Main thread");
                myRunnable.setKey();
            }
        }
    }
    
3. Implement Callable interface
public class MyCallable implements Callable<Boolean>{
    @Override
    public Boolean call(){
        return true;
    }
    public static void main(String[]args){
        MyCallable c1 = new MyCallable();
        MyCallable c2 = new MyCallable();
        
        ExecutorService service = Executors.newFixedThreadPool(2);
        
        Future<Boolean> result1 = service.submit(c1);
        Future<Boolean> result2 = service.submit(c1);
        
        Boolean b1 = result1.get();
        Boolean b2 = result2.get();
        
        service.shutdownNow();
    }
}
  • The return value type is required to implement the Callable interface

  • Overriding the call method requires throwing an exception

  • Create target method

  • Create execution service: executorservice service = executors newFixedThreadPool(1);// Create 1 service

  • Submit for execution: future < Boolean > result = service submit(thread);// Add service thread

  • Get result: Boolean B = result get();

  • Close service: service shutdownNow();

3, Thread state

1. Thread stop

stop() method// Not recommended, deprecated by jdk

2. Thread hibernation
  • sleep(int time);
  • There is an InterruptedException exception;
  • After the sleep time is completed, the thread enters the ready state and waits for CPU scheduling;
  • The thread calling the sleep() method will not release the lock, and the lock is still occupied by the caller;
3. Thread pause
  • yield();
  • The thread enters the ready state without blocking;
  • The thread re enters the CPU ready queue and waits for CPU scheduling;
  • The caller releases the lock.
4. Merge threads
  • join();
  • After inserting a thread, other threads enter the blocking queue. After the execution of the inserted thread is completed, the CPU resources are released, and then enter the ready queue for processor scheduling.
5. Thread status

Thread.State:

  • NEW: the thread was created successfully but not started;
  • RUNNABLE: the thread is executing in the JVM;
  • BLOCK: thread blocking, waiting for the monitor to lock;
  • WAITING: wait for the CPU to release resources. At this time, the resources are occupied by other threads;
  • TIMED_WAITING: a thread that waits for other threads to perform a specific action for a specific time;
  • TERMINATED: thread execution has completed and the thread is dead. At this point, the thread loses the star() opportunity.
6. Thread priority

Thread.Priority:

  • MIN_PRIORITY = 1;// Minimum priority
  • MAX_PRIORITY = 10;// Maximum priority
  • NORM_PRIORITY = 5;// Default Priority

Priority means the probability of obtaining CPU scheduling. High priority threads waiting for low priority threads to release resources will cause performance inversion;

Set priority: setPriority(int num);

Get priority: getPriority();

7. Daemon thread

Thread.daemon:

  • User thread

  • Daemon thread

    1. The JVM ensures that the user thread has finished executing
    2. The JVM does not need to wait for the execution of the daemon thread to complete
    3. Daemon threads generally include: operation log, monitoring memory, GC garbage collection, etc.

4, Thread synchronization

Multiple threads operate on the same resource

1. Concurrency and parallelism
  1. Concurrency: when the thread code of a time period is running, other threads are suspended;
  2. Parallelism: when one CPU executes one thread, the other CPU can execute another thread. The two threads do not preempt CPU resources and can execute at the same time
2. Queues and locks

When a thread obtains an object exclusive lock and monopolizes resources, if other threads need to obtain the object at this time, they must wait for the thread to release the lock in order to have the opportunity to obtain the lock.

  • A thread holding a lock will cause other processes competing for the lock to hang;
  • In the multi-threaded scenario, locking and releasing locks will lead to process context switching and CPU scheduling delay, resulting in performance problems;
  • Similarly, performance inversion can cause performance problems.
3. Synchronization method

The synchronized synchronization method controls the access of resources, that is, each resource uses a lock, and each synchronized method must obtain the lock using the resource to access it. Otherwise, the thread accessing the resource needs to enter the blocking queue and wait for the release of the resource. Once the synchronization method is executed, the execution thread monopolizes the lock and does not release the lock until the execution is completed.

public synchronized void fun(){
    /*
    *Execute code block
    */
}
4. Synchronization block
public void fun(Object obj){
    synchronized(obj){
        /*
        *Execute code block
        */
    }
}
  • obj: synchronization monitor, which can be any object. Generally, shared resources are used as synchronization monitor.
  • There is no need to specify a synchronization monitor in the synchronization method. The monitor in the synchronization method is this, which is the object itself;
  • Synchronization monitor execution process:
    1. Thread 1 accesses the shared resource, locks the synchronization monitor, and starts executing the synchronization code block;
    2. At this time, thread 2 also needs to access the synchronization resource. If it finds that the synchronization monitor has been locked, the access cannot proceed, and starts waiting for the synchronization monitor to be released;
    3. When thread 1 executes the synchronization code block, thread 1 immediately releases the synchronization monitor and unlocks the resources;
    4. Thread 2 accesses the shared resources, locks the synchronization monitor and enters the synchronization code block;
    5. Thread 2 accesses the synchronization resources, releases the synchronization monitor and contacts the occupation of shared resources.
5. Deadlock
  1. Multiple threads occupy shared resources and wait for the resources occupied by other threads to run;
  2. Deadlock causes two or more threads to wait for each other to release resources;
  3. Four necessary conditions for deadlock generation:
    • Mutually exclusive condition: only one process is allowed to use a resource at a time;
    • Request and hold: when a process requests and holds, the obtained resources remain unchanged;
    • Inalienable conditions: when the process has not used up the resources obtained, it cannot be forcibly deprived;
    • Circular waiting condition: a circular waiting resource relationship is formed between several resources.
6.Lock lock
  1. Lock and release lock

    ReentrantLock lock = new ReentrantLock();
    public void fun(){
        lock.lock();//Explicit locking
        /*
        *Synchronous code block
        */
        lock.unlock();//Explicit unlock
    }
    
  2. Lock lock realizes synchronization by explicitly defining the lock object;

  3. Lock lock provides exclusive access to shared resources, that is, only one thread locks the lock object at a time. Threads need to obtain the lock object before accessing shared resources;

  4. ReentrantLock implements the Lock interface and has the same concurrency and memory semantics as synchronized. It is commonly used in thread safety control;

  5. Comparison between synchronized and Lock:

    • Lock is a display lock (manual switch lock), while synchronized is an implicit lock, which is automatically released when leaving the scope;
    • Lock only has code block lock, while synchronized has method lock and code block lock;
    • Using Lock lock, the JVM spends less time scheduling threads;
    • Priority of locks: Lock > sync code block > sync method.

5, Thread communication

1. Method of thread communication
  • wait();// Thread waiting
  • notify();// notice
  • notifyAll();// Notify all other threads
2. Producer consumer model

(1) Pipe program method
//Product category
public class Product{
    String name;
    public Product() {
    }
    public Product(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
//consumer
public class User implements Runnable{
    Factory factory;
    User(){}
    User(Factory factory){
        this.factory = factory;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) 
            factory.getProduct();
    }
}
//producer
public class Productor implements Runnable{
    Factory factory;
    Productor(){}
    Productor(Factory factory){
        this.factory = factory;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++)
            factory.setProduct(new Product("product:"+(i+1)));
    }
}

//Factory class
public class Factory{

    final int PRODUCT_MAX_NUM = 10;
    //Product collection
    Product [] products = new Product[PRODUCT_MAX_NUM];

    //Product counter
    int proCount=0;

    //yield a product
    public synchronized void setProduct(Product item){
        if(proCount == PRODUCT_MAX_NUM-1){
            System.out.println("When the product reaches the inventory, stop production...");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        products[proCount] = item;
        proCount++;
        System.out.println("The producer produced the product:"+item.getName()+",Inform consumers of consumption...");
        notifyAll();
    }
    //Consumer products
    public synchronized Product getProduct(){
        if (proCount == 0){
            System.out.println("There are no products to consume...");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Product item = products[proCount-1];

        System.out.println("Consumers consume products:"+item.getName());
        notifyAll();
        proCount--;
        return item;
    }
    
    public static void main(String[] args) {
        Factory factory = new Factory();

        new Thread(new Productor(factory)).start();
        new Thread(new User(factory)).start();
    }
}
(2) Signal lamp method
//Factory class
public class Factory{

    final int PRODUCT_MAX_NUM = 10;
    //Product collection
    Product [] products = new Product[PRODUCT_MAX_NUM];

    //Product counter
    int proCount=0;

    //Semaphore,
    boolean single = false;

    //yield a product
    public synchronized void setProduct(Product item){
        if(proCount == PRODUCT_MAX_NUM){
            single = true;
            System.out.println("When the product reaches the inventory, stop production...");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        products[proCount] = item;
        single = true;
        proCount++;
        System.out.println("The producer produced the product:"+item.getName()+",Inform consumers of consumption...");
        notifyAll();
    }
    //Consumer products
    public synchronized Product getProduct(){
        if (!single){
            System.out.println("There are no products to consume...");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Product item = products[proCount-1];

        System.out.println("Consumers consume products:"+item.getName());
        notifyAll();
        single = --proCount != 0;
        return item;
    }
    public static void main(String[] args) {
        Factory factory = new Factory();

        new Thread(new Productor(factory)).start();
        new Thread(new User(factory)).start();
    }
}
3. Thread pool
//1 - directly create a thread pool instance using the ThreadPoolExecutor() construction method
ExecutorService es = new ThreadPoolExecutor(
  10,//corePoolSize: thread core pool size 1
  10,//maximunPoolSize: the maximum number of threads is 10
  10L,//keepAliveTime: the thread stops after 10 milliseconds after it has no task
  TimeUnit.MICROSECONDS,//In ms
  new LinkedBlockingQueue<Runnable>()
);

//2 - create a thread pool with a fixed capacity of 10
ExecutorService es1 = Executors.newFixedThreadPool(10);
/*Source code
public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor(
 		 nThreads, 
 		 nThreads,
       0L, 
       TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>());
  }
*/

//3 - create a cacheable thread pool without changing the size of the thread pool,
//  The thread pool size depends on the maximum number of threads that the OS or JVM can create
//  The thread stops after 60 seconds without a task
ExecutorService es2 = Executors.newCachedThreadPool();
/*Source code
public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(
        0, 
  	  Integer.MAX_VALUE,
        60L, 
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());
}
*/
//4 - create a singleton thread pool and use a unique worker thread to execute tasks
ExecutorService es3 = Executors.newSingleThreadExecutor();
/*Source code
public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService(
      new ThreadPoolExecutor(
          1, 
          1,
          0L, 
          TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue<Runnable>()));
}
*/

//Create a fixed size thread pool to support timed periodic tasks
ExecutorService es4 = Executors.newScheduledThreadPool(10);
/*Source code
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  return new ScheduledThreadPoolExecutor(corePoolSize);
}
*/

  • advantage:

    • Improve response speed
    • Reduce resource consumption
    • Easy thread management
  • Content:

    • ExecutorService and Executors;
    • corePoolSize: the size of the thread core pool;
    • maximunPoolSize: maximum number of threads;
    • keepAliveTime: how long does the thread stop after it has no task
  • ThreadPoolExecutor is a subclass of ExecutorService

    • Void executor (Runnable): executes a task without a return value, and the transfer parameter is Runnable;

       ExecutorService es = Executors.newFixedThreadPool(10);//Create a thread pool with a capacity of 10
       es.submit(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("Runnable Interface");
                  }
              });
      //es. submit(()-> System. out. Println ("runnable interface")// Use lamda expression abbreviation
      es.shutdown();//Close thread pool
      
    • < T > Future < T > submit (Callable < T > able): execute the task with the return value, and the transfer parameter is Callable;

              ExecutorService es = Executors.newFixedThreadPool(10);
      
              FutureTask<String> ft = new FutureTask<String>(new Callable<String>() {
                  @Override
                  public String call() throws Exception {
                      return "Callable Interface";
                  }
              });
      
              es.submit(ft);
      
              try {
                  System.out.println(ft.get());//Get execution results
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (ExecutionException e) {
                  e.printStackTrace();
              }
      
              es.shutdown();//Close thread pool
      
  • Executors: tool class, thread pool factory class, used to create and return different types of thread pools;

summary

Well, that's all for multithreading!
java Where does this road lead? Where should Zhang San go?
Everything is uncertain!
All we know is that he took the book,
It wasn't just a few pieces of paper,
What he took away was his youth and endless "happiness",
Zhang San might say:"No, it is java Multithreading? I'll do it every minute!"

But he will never know that the consequences of waiting for him are definitely not the results he wants, and that aunt, she will use her life to cure her lost java from getting started to giving up. Yes, Zhang San lost. He didn't lose to the interviewer or multi-line! He failed completely, he failed all over the place!

Topics: Java jvm Multithreading Operating System Concurrent Programming