Java multithreading is a very important feature of the Java language, which allows programs to perform multiple tasks at the same time. Through multithreading, a program can handle multiple tasks at the same time, thereby shortening the execution time of the program. In addition, multi-threading also helps to utilize multi-core processors to better utilize the performance of computer hardware.
So how do we implement multi-threading in Java?
Thread
Today we will talk about it from the simplest Runnable
to the more commonly used ones today.CompletableFuture
Thread
Threads are created in Java by creating Thread
an instance of a class. The simplest way to create a thread is to extend Thread
the class and override run()
the method. Write the code to be executed in run()
the method, and then call the method in the program start()
to start the thread. For example:
public class MyThread extends Thread {
public void run() {
// 线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
}
}
}
public static void main(String[] args) {
System.out.println("start");
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
System.out.println("end");
}
Of course, if you find it troublesome, you can directly use anonymous inner classes
// 效果是一样的
System.out.println("start");
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
}
}).start();
System.out.println("end");
Note here that you must not use run()
methods, which will make the threads synchronized. At the same time, if you want to execute the subsequent code after the multi-threaded content is executed, then you can use join()
this method, which can make the thread finish executing. Then go to perform the following operations.
Of course, you can also use it sleep()
to let the main thread sleep for a period of time. Of course, this sleep time is subjective and is determined by ourselves. This method is not recommended.
Runnable
In addition, we can also use Runnable
interfaces to create threads. Runnable
The interface has only one run()
method, in which you write the code to be executed, then Runnable
pass the object as a parameter to Thread
the constructor of the class and call start()
the method to start the thread. For example:
public class MyThread implements Runnable {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
}
}
}
In fact, it Thread
is almost the same. It is just an implementation interface and an inheritance class. If used directly, there is not much difference between the two.
However, some places will say that Runnable
it is easy to realize resource sharing, but Thread
it is not. However, after my testing, I think Thread
resource sharing can also be realized.
test code
public class MyThread extends Thread {
private int ticket = 5;
@Override
public void run() {
// 双检索机制——保证线程的安全
if (ticket > 0) {
synchronized (this) {
if (ticket > 0) {
while (true) {
System.out.println("Thread:" + Thread.currentThread().getName() + "--Thread ticket = " + ticket--);
if (ticket < 0) {
break;
}
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
}
}
执行结果如下:
Thread:Thread-1--Thread ticket = 5
Thread:Thread-1--Thread ticket = 4
Thread:Thread-1--Thread ticket = 3
Thread:Thread-1--Thread ticket = 2
Thread:Thread-1--Thread ticket = 1
Thread:Thread-1--Thread ticket = 0
When we looked at Thread
the source code, we found that: in fact, Thread
it just implements Runnable
the interface and provides more methods. So there is no difference Thread
between and. Runnable
If there is any difference, it would be the difference between classes and interfaces, and the difference between inheritance and implementation.
Callable (with Future)
For child threads, we sometimes may have two needs:
- Obtain the running result of the child thread
- Get the running status of the child thread (success, failure, exception)
Thread
Neither and Runnable
do not meet these two requirements. Runnable
The status can be obtained but the result cannot be obtained, so it appears Callable
. Callable
Used together Future
, the execution result of the sub-thread can be obtained.
( Future
It is an asynchronous calculation method in Java multi-threading, which can be used to obtain the results of asynchronous calculations during task execution)
public class MyThread implements Callable<Integer> {
private Integer number;
public Integer getNumber(){
return number;}
public MyThread (Integer number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
}
}
public static void main(String[] args) throws Exception {
System.out.println("start");
MyThread myThread1= new MyThread (10);
MyThread myThread2= new MyThread (15);
//使用Executors工厂类创建一个简单的线程池(线程数:2)
ExecutorService executor = Executors.newFixedThreadPool(2);
// 将任务提交给线程池
Future<Integer> submit1 = executor.submit(myThread1);
Future<Integer> submit2 = executor.submit(myThread2);
// 获取任务的执行结果
System.err.println(submit1.get());
System.err.println(submit2.get());
executor.shutdown();
System.out.println("end");
}
In this example, we implement MyThread
the class, which implements Callable
the interface and overrides call()
the methods. In call()
the method, we simulate a long-running task and return a calculation result as the execution result. In the main method, we first create a thread pool with a fixed number of threads ExecutorService
, and then MyThread
pass the instance into submit
the method to submit the task and get an Future
object of type. By calling get()
the method of this object, we can obtain the execution results of the task. Finally, we turn off the thread pool.
It should be noted that since the called Future
method get()
is blocking, we may need to process it try-catch
. Also, after completing the task, we have to close the thread pool to release the resources.
This is a simple usage Callable
example, but Future
more complex concurrent computations can be achieved by combining multiple objects.
Future
Of course Future
it can also be used alone.
When in use Future
, you can call get()
a method to block waiting for the task to be executed and obtain the calculation result; you can also call isDone()
a method to determine whether the task has been completed; if an exception occurs during task execution, you can get()
obtain the exception information by calling the method
public static void main(String[] args) throws Exception {
System.out.println("start");
ExecutorService executor = Executors.newFixedThreadPool(2);
Future future1 = executor.submit(() -> {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
});
Future future2 = executor.submit(() -> {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
});
String result1 = (String) future1.get();
String result2 = (String) future2.get();
System.out.println(result1);
System.out.println(result2);
executor.shutdown();
System.out.println("end");
}
In fact, it is the same as the previous method, except that call()
the business logic in the method is redundant into the main code, and the coupling is higher. If only some simple codes can be used, if it is complicated, it is recommended to callable
use them together for comparison. good
CompletableFuture
CompletableFuture
is a very useful feature introduced in Java 8 that allows us to handle asynchronous operations more easily, especially when these operations involve multiple stages.
CompletableFuture
Inherited from the Future
interface, the calculation results can be obtained after the calculation is completed, so it also has Future
the characteristics of . In addition, it provides some other features:
- Asynchronous execution and serial execution functions
- The observer mechanism can be triggered after the task is executed
- Two tasks can be combined and executed together to handle more complex asynchronous scenarios.
Nowadays, it is common to use more complex logic in development.CompletableFuture
create
CompletableFuture
We can use runAsync()
the method to directly execute asynchronously
CompletableFuture.runAsync(() -> {
// 在这里执行一些耗时的操作
});
CompletableFuture
The result of asynchronous processing
We can use CompletableFuture
the static method supplyAsync()
to create an asynchronous execution CompletableFuture
and provide an lambda
expression as its calculation generator:
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "some result";
});
completableFuture.thenAccept(result -> {
System.out.println("Got result: " + result);
});
// 这里可以继续执行其他任务,completableFuture将在后台继续执行并在完成后触发回调函数。
Operate two or more
CompletableFuture
If we need to wait for multiple CompletableFuture
tasks to be completed before executing certain tasks, then we can use CompletableFuture
static methods allOf()
or anyOf()
:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "Result of future 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "Result of future 2";
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> {
//这边join和get方法都可以 只是抛出异常的区别
String result1 = future1.join();
String result2 = future2.join();
System.out.println("Got both results: " + result1 + " and " + result2);
});
CompletableFuture
The thread pool is used by defaultForkJoinPool
. If you want to define a thread pool yourself, you can createExecutors
one directly. However,Executors
it is a factory class that provides some static methods for creating thread pools, not a thread pool itself, so the thread cannot be set. Detailed parameters such as: number of core threads, maximum number of threads, rejection policy, etc. If you need to set these parameters, you shouldThreadPoolExecutor
manually create the thread pool using the
/**
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue,
* ThreadFactory threadFactory,
* RejectedExecutionHandler handler) {}
* 1. corePoolSize:核心线程数;当提交的任务数大于 corePoolSize 时,线程池会自动扩容。
*
* 2. maximumPoolSize:线程池最大线程数;当活动线程数达到该值,并且 workQueue 队列已满,则执行拒绝策略。
*
* 3. keepAliveTime:线程空闲超时时间,超过该时间则进行回收。
*
* 4. unit:keepAliveTime 的时间单位。
*
* 5. workQueue:任务阻塞队列,用于存储提交但尚未执行的任务。
*
* 6. threadFactory:线程工厂,用于创建线程。
*
* 7. handler:拒绝策略,当线程数量已经达到 maximumPoolSize 并且队列已满时,采取的策略。
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
10,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
There
springboot
is a method that is also used very frequently -@async
you only need to add this annotation to the method and add it to the startup class@Enableasync
to directly implement multi-threaded exception operations. However, this multi-threaded method is still somewhat different from the one mentioned above. The difference@async
is that when calling a method, multi-threaded asynchronous operations are performed, but the business logic in the method is still synchronous, so it can be used together normally (ofComplatableFuture
course, you can also extract the business logic in the middle and add a@async
hahahaha) )
Java's multi-threading mechanism is an indispensable part of Java programming. Understanding and being familiar with Java's multi-threading programming capabilities will greatly improve programmers' work efficiency and programming level.