How Java Threads Are Created and Best Practices

The first step in concurrent programming is to create threads, so how to create threads is also a question that interviewers often ask during recruitment. This article introduces several common thread creation methods in Java, and summarizes them.

how to create

1 Implement the Runnable interface

public class RunnableThread implements Runnable {
    
    
    @Override
    public void run() {
    
    
        System.out.println("用实现Runnable接口实现线程");
    }
}

The first way is to implement multithreading by implementing the Runnable interface. As shown in the code, first implement the Runnable interface through the RunnableThread class, then rewrite the run() method, and then just pass the instance that implements the run() method to Thread Multithreading can be implemented in the class.

2 Inherit the Thread class

public class ExtendsThread extends Thread {
    
    

    @Override
    public void run() {
    
    
        System.out.println("用Thread类实现线程");
    }
}

The second way is to inherit the Thread class, as shown in the code, the difference from the first way is that it does not implement the interface, but inherits the Thread class and overrides the run() method. I believe that you must be very familiar with the above two methods and use them often in your work

3 thread pools

So why is there a third or fourth way? Let's first look at the third way: creating threads through the thread pool. The thread pool does implement multi-threading. For example, if we set the number of threads in the thread pool to 10, then there will be 10 sub-threads to work for us. Next, we will deeply analyze the source code in the thread pool to see if the thread pool is How to achieve the thread?

private static class DefaultThreadFactory implements ThreadFactory {
    
    
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
    
    
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup():Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
    }

    public Thread newThread(Runnable r) {
    
    
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

For the thread pool, the thread is essentially created through the thread factory. DefaultThreadFactory is used by default, which sets some default values ​​for the thread created by the thread pool, such as the name of the thread, whether it is a daemon thread, and the priority of the thread, etc. . But no matter how these properties are set, it still creates a thread through new Thread() in the end, but the constructor here has more parameters passed in. It can be seen that the thread creation through the thread pool does not deviate from the original one. Two basic creation methods, because they are essentially implemented through new Thread().

In the interview, if you only know that you can create threads in this way but do not understand the implementation principle behind it, you will struggle in the interview process, and you will dig a "pit" for yourself if you want to express yourself better.

So when we answer the question of thread implementation, after describing the first two methods, we can further extend to say "I also know that thread pools and Callables can also create threads, but they are also essentially thread creation through the first two basic methods. .” Such an answer will be a bonus in the interview. Then the interviewer will most likely ask about the composition and principle of the thread pool, which will be analyzed in detail in the following class hours.

4 Inherit Callable

class CallableTask implements Callable<Integer> {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        return new Random().nextInt();
    }
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());

The fourth thread creation method is to create a thread through a Callable with a return value. Runnable creates a thread without a return value, while Callable and its related Future and FutureTask can return the result of thread execution as a return value, such as code As shown, implement the Callable interface, and set its generic type to Integer, and then it will return a random number.

However, whether it is Callable or FutureTask, they are first and foremost like Runnable, they are all tasks that need to be executed, not that they are threads themselves. They can be executed in the thread pool. As shown in the code, the submit() method puts the task into the thread pool, and the thread pool creates the thread. No matter what method is used, it is ultimately executed by the thread, and the child thread The creation method of Thread is still inseparable from the two basic methods mentioned at the beginning, that is, implementing the Runnable interface and inheriting the Thread class.

5 Other ways to create

At this point you might say that I know of some other ways to implement threads. For example, a timer can also implement threads. If you create a new Timer and make it perform some tasks every 10 seconds or after setting it for two hours, then it does create threads and perform tasks at this time, but if we analyze in depth The source code of the timer will find that in essence, it will still have a TimerThread that inherits from the Thread class, so the timer creation thread finally rewinds to the two methods mentioned at the beginning.

Maybe you will also say that I also know some other ways, such as anonymous inner classes or lambda expressions, in fact, anonymous inner classes or lambda expressions create threads, they only implement threads at the syntactic level, and cannot put It comes down to the way of implementing multi-threading, as shown in the code of anonymous inner class implementing thread, it just uses an anonymous inner class to give the instance the Runnable that needs to be passed in.

Essence: all created by Thread

In addition to the first and second creation methods, such as thread pools or timers, they are just a layer of encapsulation based on the first two methods. If we call these a new method, Then there are endless ways to create threads. For example, after the JDK version is updated, there may be several more classes, and new Thread() will be repackaged. On the surface, it will be a new way to implement threads. But looking at the essence through the phenomenon, after opening the package, you will find that they are ultimately implemented based on the Runnable interface or inheriting the Thread class. And by implementing Runnable class, in order to truly run in a multi-threaded environment, it is also necessary to pass its instance to the Thread object. It can be seen that the Runnable interface only defines a specification. The essence of the real thread creation is still completed by the Thread class.

Best Practice: Implementing the Runnable Interface

In the actual coding process, in order to facilitate decoupling and inheritance of other classes, it is recommended to define the content that needs to be executed under multi-threading by implementing the Runnable interface. The specific reasons are as follows:

First of all, we consider the architecture of the code. In fact, there is only one run() method in Runnable, which defines what needs to be executed. In this case, the decoupling between Runnable and Thread class is realized, and the Thread class is responsible for thread startup. and attribute settings, with clear rights and responsibilities.

Secondly, in some cases, performance can be improved. Using the inheritance Thread class method, each time a task is executed, a new independent thread needs to be created. If we use the method of implementing the Runnable interface, we can directly transfer the task to the thread. The pool uses some fixed threads to complete tasks, and does not need to create and destroy threads every time, which greatly reduces performance overhead.

Finally, the advantage is that the Java language does not support double inheritance. If our class inherits the Thread class, it will not be able to inherit other classes in the future. In this way, if this class needs to inherit other classes in the future to achieve some functional Expansion, it has no way to do it, which is equivalent to limiting the future scalability of the code.

In summary, we should prefer to create threads by implementing the Runnable interface.

Guess you like

Origin blog.csdn.net/monarch91/article/details/123341817