Java多线程是Java语言中一个非常重要的特性,它允许程序同时执行多个任务。通过多线程,程序可以同时处理多项任务,从而缩短程序的执行时间。另外,多线程也有助于利用多核处理器,更好地发挥计算机硬件的性能。
那我们在Java中如何去实现多线程呢?
今天我们就从最简单的的Thread
、Runnable
讲到现在比较常用的CompletableFuture
Thread
Java中通过创建Thread
类的实例来创建线程。创建线程的最简单方式是扩展Thread
类并覆盖run()
方法。在run()
方法中编写要执行的代码,然后在程序中调用start()
方法启动该线程。例如:
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");
}
当然你要是嫌麻烦的话你可以直接使用匿名内部类
// 效果是一样的
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");
这边注意千万不用使用run()
方法,这会使线程变成同步,同时如果你想让多线程的内容执行完之后再去执行之后的代码,那你可以使用join()
这个方法,它可以使该线程执行完之后再去执行下面的操作。
当然你也可以使用sleep()
让主线程睡眠一段时间,当然这个睡眠时间是主观的时间,是我们自己定的,这个方法不推荐
Runnable
此外,我们还可以使用Runnable
接口来创建线程。Runnable
接口只有一个run()
方法,在其中编写要执行的代码,然后将Runnable
对象作为参数传递给Thread
类的构造函数,并调用start()
方法启动线程。例如:
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 + "}");
}
}
}
其实和Thread
差不多,只是一个实现接口,一个继承类,直接使用的话这两个没有太大的区别
不过有的地方会说Runnable
便于实现资源共享,而Thread
不能,但是经过的我的测试认为Thread
也可以实现资源共享
测试代码
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
我们看Thread
的源代码发现:其实Thread
也就是实现了Runnable
接口,提供了更多的方法而已。所以说Thread
与Runnable
并没有什么区别。如果硬要说有什么区别的话,那就是类与接口的区别,继承与实现的区别
Callable(搭配Future)
对于子线程,我们有时候可能会有两种需求:
- 获取子线程运行结果
- 获取子线程运行状态(成功、失败、异常)
Thread
和Runnable
都不满足这两个要求,Runnable
可以获取状态但不能获取结果,于是出现了Callable
。Callable
配合Future
使用可以获得子线程执行结果。
(Future
是Java多线程中的一种异步计算方式,可以用来获取在执行任务期间进行异步计算的结果)
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");
}
在这个例子中,我们实现了MyThread
类,它实现了Callable
接口,并重写了call()
方法。在call()
方法中,我们模拟了一个长时间运行的任务,并返回一个计算结果作为执行结果。在main方法中,我们首先创建了一个固定线程数的线程池ExecutorService
,然后将MyThread
的实例传入submit
方法来提交任务,并得到一个Future
类型的对象。通过调用该对象的get()
方法,我们可以获取任务的执行结果。最后,我们关闭了线程池。
需要注意的是,由于调用Future
的get()
方法是阻塞的,所以我们可能需要对其进行try-catch
处理。此外,在完成任务后,我们必须关闭线程池以释放资源。
这是一个简单的使用Callable
的示例,通过组合多个Future
对象可以实现更复杂的并发计算。
Future
当然 Future
也可以进行单独使用。
在使用Future
时,可以调用get()
方法来阻塞等待任务执行完毕并获取计算结果;也可以调用isDone()
方法来判断任务是否执行完毕;如果任务执行过程中发生异常,可以通过调用get()
方法获取异常信息
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");
}
其实和上一个方法是一样的,只不过是这边把call()
方法中的业务逻辑冗余到了主代码中,耦合性更高了,如果只是一些简单的代码可以使用,复杂的话还是建议搭配callable
一起使用比较好
CompletableFuture
CompletableFuture
是Java 8引入的一个非常有用的功能,它可以让我们更轻松地处理异步操作,特别是当这些操作涉及到多个阶段时。CompletableFuture
继承自 Future
接口,可以在计算完成后获取计算结果,因此它也具备了 Future
的特性。除此之外,它还提供了一些其他的特性:
- 异步执行和串行执行功能
- 任务执行完毕后可以触发观察者机制
- 可以将两个任务组合到一起执行等,处理更加复杂的异步场景。
现在开发来说使用较为复杂的逻辑普遍会使用CompletableFuture
创建
CompletableFuture
我们可以使用runAsync()
方法直接去异步执行
CompletableFuture.runAsync(() -> {
// 在这里执行一些耗时的操作
});
异步处理
CompletableFuture
的结果
我们可以使用CompletableFuture
的静态方法supplyAsync()
创建一个异步执行的CompletableFuture
,并提供一个lambda
表达式作为其计算发生器:
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "some result";
});
completableFuture.thenAccept(result -> {
System.out.println("Got result: " + result);
});
// 这里可以继续执行其他任务,completableFuture将在后台继续执行并在完成后触发回调函数。
操作两个或多个
CompletableFuture
如果我们需要等待多个CompletableFuture
都完成后执行某些任务,那么我们可以使用CompletableFuture
的静态方法allOf()
或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
默认使用的是ForkJoinPool
线程池,如果你想自己定义线程池的话可以使用Executors
直接创建一个,但是Executors
是一个工厂类,提供了一些静态方法用于创建线程池,而不是一个线程池本身,因此无法设置线程的详细参数例如:核心线程数,最大线程数,拒绝策略等。如果需要设置这些参数,应该使用ThreadPoolExecutor
类来手动创建线程池
/**
* 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());
在
springboot
中有个方法也用的很频繁——@async
,只需要在方法上加上这个注解并在启动类上加上@Enableasync
就可以直接实现多线程异常操作,不过这个多线程的方法和上面讲的还是有些区别的,@async
是调用方法的时候进行多线程异步操作,但是方法中的业务逻辑还是同步的,所以正常可以搭配ComplatableFuture
一起使用(当然也可以吧中间的业务逻辑提取出方法再加个@async
哈哈哈哈 都可以的)文章来源:https://www.toymoban.com/news/detail-429791.html
Java的多线程机制是Java编程中不可或缺的一部分,了解和熟悉Java多线程编程能力将极大地提升程序员的工作效率和编程水平。文章来源地址https://www.toymoban.com/news/detail-429791.html
到了这里,关于浅谈之Java多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!