[JUC Basics] 01. Preliminary understanding of JUC

Table of contents

1 Introduction

2. What is JUC?

3. Parallel and concurrent

4. Processes and threads

5. How to create sub-threads

5.1. Inherit Thread

5.2. Realize Runnable

5.3, implement Callable

5.4 Summary

6、Thread和Runnable

7、Runnable和Callable

8. Thread state

9. Summary


1 Introduction

Some time ago, a friend asked me if I could write some tutorial articles about JUC. Originally, JUC was also included in my column plan, but it was never his turn, so since there is such an opportunity, let’s advance the JUC plan. So today we will focus on getting a preliminary understanding of what is JUC and some basic knowledge about JUC.

Regarding JUC, it is recommended to learn with Java API (JAVA8 is used in this article). Java API download direct link: https://download.csdn.net/download/p793049488/87743633

2. What is JUC?

JUC (java.util.concurrent) is a toolkit built into JDK to handle concurrent. It has appeared since JDK1.5. Many tool classes and interfaces commonly used in concurrent programming have been added to this package, including thread pools, atomic classes, locks, and concurrent containers. These tool classes and interfaces can simplify the complexity of multi-thread programming and improve the concurrent performance and reliability of the program.

It contains some of our common tool classes, such as

  • Callable
  • ExecutorService
  • ThreadFactory
  • ConcurrentHashMap
  • ......

These will be mentioned one by one later.

JUC mainly includes three modules:

 

3. Parallel and concurrent

We mentioned earlier that JUC is a toolkit for dealing with concurrent programming problems. So what is concurrency? Compared with concurrency, people often hear more about parallelism, so what is the difference between parallelism and concurrency?

Here we have to invite our gold medal teacher Mr. C (ChatGPT) to tell you about it:

A brief summary is:

  • Parallel: At the same time, the tasks are performed at the same time. Emphasis on "simultaneously".
  • Concurrency: Use the time waiting for some things to complete to alternately complete other things. Not necessarily at the same time. More emphasis on "alternate".

To give a simple example:

Suppose you need to make a lunch. You can prepare multiple ingredients such as rice, vegetables, and soup at the same time, and then cook and process them alternately. This is concurrency.

If you have an oven and a gas range, you can bake bread in the oven and cook soup on the gas range at the same time, that's parallelism.

4. Processes and threads

  • Process (process) refers to an instance of a running program in the operating system, which has its own independent space and resources, including memory, files, network, etc. A process can consist of one or more threads. If you open the task manager of the computer, you can see a detailed list of each running task.

  • Thread refers to the smallest unit of scheduling execution in the operating system, and is an execution unit in a process. Multiple threads in a process can share process resources, such as memory, files, and so on.

Following are the main differences between threads and processes:

  1. Resource occupation: A process occupies independent system resources, including memory, files, network, etc., while threads run within the process, and multiple threads can share the resources of the process to reduce resource occupation.
  2. Switching overhead: The overhead of thread switching is smaller than that of processes, because threads are scheduled within the process, while process switching needs to save and restore the state of the process, which is more expensive than thread switching.
  3. Communication method: Threads in the same process can communicate through shared memory, etc., while communication between different processes requires the use of inter-process communication mechanisms, such as pipes, message queues, etc.
  4. Execution independence: Processes are independent, the crash of one process will not affect the execution of other processes, and the resources of the process are shared between threads, the crash of one thread may cause the entire process to crash.
  5. System overhead: Since the process has its own independent resources, switching between processes requires more system overhead, while threads share the resources of the process, and the switching overhead is smaller.

In general,

A process is the basic unit of program resource scheduling.

A thread is the basic unit of CPU execution.

5. How to create sub-threads

5.1. Inherit Thread

package com.github.fastdev;

public class Main {
    public static void main(String[] args) {
        new MyThread1("我是继承Thread的线程").start();
    }
}

class MyThread1 extends Thread {
    private String name;

    public MyThread1(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("Thread-1 " + name + " is running.");
    }
}

5.2. Realize Runnable

package com.github.fastdev;

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread2("我是实现Runnable的线程")).start();
    }
}


class MyThread2 implements Runnable {
    private String name;

    public MyThread2(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("Thread-2 " + name + " is running.");
    }

}

5.3, implement Callable

package com.github.fastdev;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 由于new Thread构造函数无法接收callable。这里使用线程池的方式调用
        ExecutorService executor = Executors.newFixedThreadPool(1);
        executor.submit(new MyThread3("我是实现Callable的线程"));
    }
}

class MyThread3<String> implements Callable<String> {
    private String name;

    public MyThread3(String name) {
        this.name = name;
    }

    @Override    
    public String call() throws Exception {
        System.out.println("Thread-3 " + name + " is running.");
        return (String) "创建成功";
        
    }
}

5.4 Summary

There are two methods in Thread, start() and run(). When we use multi-threaded concurrency, we should use the start() method instead of the run() method.

start() is used to start a thread and execute the run method in the thread. A thread can only be started once.

run() is used to execute in this thread, it is just a method of common class and can be called repeatedly many times. If run() is called in the main thread, then the meaning of concurrency is lost.

6、Thread和Runnable

From the above code, we can see that to achieve a multi-threaded programming. There are several steps:

  1. To create a child thread, choose one of the three methods 5.1-5.3 to create it.
  2. new Thread(), passing the execution thread to the Thread constructor.
  3. Call the start() method.

So since Runnable or Callable can already create a child thread, why do you need new Thread and call its start()?

By looking at the source code of Thread, we can see that Thread itself is actually an extension of Runnable:

 And Thread extends a series of thread operation methods, such as start(), stop(), yeild()...

And Runnable is just a functional interface. Note that it is just an interface, and it has only one method: run().

The official comment also clearly tells everyone that Runnable should be implemented by any class. This interface aims to provide a public protocol for objects that want to execute code in an active state. In most cases, the run() method should be rewritten by subclasses.

Therefore, Thread is just an implementation of Runnable, which extends a series of methods to manipulate threads. My understanding is that the existence of Runnable is to provide subclass extensions for thread operations more conveniently. For object-oriented programming, such extensions are necessary. Many people on the Internet say that "Runnable is easier to share resources among multiple threads, but Thread cannot". This sentence is different. The existence of the Runnable interface allows you to freely define many reusable thread implementation classes. In line with object-oriented thinking.

7、Runnable和Callable

This question is almost a must-ask question in the basics of JUC interviews. Since Runnable can realize the operation of sub-threads and is also in line with object-oriented thinking, why is Callable needed. And the new Thread constructor does not support passing in a Callable, so what is the significance of Callable?

The answer is: existence is reasonable.

First look at the Callable source code:

It can be seen from the source code that the difference between Callable and Runnable is:

  1. The return value of Runnable is void, and the return value of Callable is a generic type.
  2. The default built-in method of Runnable is run, and the default method of Callable is call.
  3. Runnable does not throw an exception by default, and Callable throws an exception.

And it turns out that this is indeed the case, not only the source code says so, but also the official documentation says:

When we need the execution status of a certain thread, or need to customize the exception handling of the thread, or need to obtain the feedback result of multi-thread. We need to use Callable.

Code example:

package com.github.fastdev;

import java.util.concurrent.*;
import java.lang.String;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<String> future = executor.submit(new MyThread3("我是实现Callable的线程"));
        System.out.println("线程返回结果:" + future.get());
    }
}


class MyThread3 implements Callable<java.lang.String> {
    private String name;

    public MyThread3(String name) {
        this.name = name;
    }

    @Override    
    public String call() throws Exception {
        System.out.println("Thread-3 " + name + " is running.");
        return "ok";
    }
}

return result:

8. Thread state

The Java language defines 6 thread states. At any point in time, a thread has one and only one state, and different states can be switched through specific methods.

  1. New (New): not yet enabled after creation
  2. Running (Runnable): including Running and Ready, threads in this state may be executing, or may be waiting for the operating system to allocate execution time
  3. Waiting indefinitely (Waiting): The thread in this state will not be allocated execution time and will wait to be explicitly woken up. A thread will be in this state under the following circumstances:
    1. Object::wait() method without setting the Timeout parameter;
    2. Object::join() method without setting the Timeout parameter;
    3. LockSupport::park() method
  4. Timed Waiting: The thread in this state will not be allocated execution time, but it does not need to wait to be explicitly awakened by other threads, and will be automatically awakened by the system after a certain period of time. A thread will be in this state under the following circumstances:
    1. Thread::sleep() method.
    2. The Object::wait() method with the Timeout parameter set.
    3. Thread::join() method with Timeout parameter set.
    4. LockSupport::parkNanos() method.
    5. LockSupport::parkUntil() method.
  5. Blocked: The thread is blocked. Among them, there are blocking state and waiting state.
    1. Blocked state: waiting to acquire an exclusive lock, this time will occur when another thread gives up the lock;
    2. Waiting state: waiting for a period of time, or the occurrence of a wake-up action. A thread enters this state while the program is waiting to enter a synchronized region.
  6. Terminated: The state of the thread is planted, and the thread finishes running.

The state transition relationship is as follows:

9. Summary

Since the advent of multiprocessors, concurrent programming has been the best way to improve system responsiveness and throughput. But it also increases the complexity of programming accordingly. Compared with single-thread, multi-thread is more full of unknowns. Once a concurrency problem occurs, sometimes it cannot be reproduced without a specific scenario. Therefore, we need to consolidate the foundation of multi-threading in order to calmly deal with a series of unknown problems brought about by multi-threading. The first part of JUC basic learning is here, introducing some common multi-threading knowledge to pave the way for later learning. Make a little progress every day.

Guess you like

Origin blog.csdn.net/p793049488/article/details/130456607