在Java中,多线程编程是一种强大的并发编程技术,可以让你同时执行多个任务。Java提供了多种方式来创建和管理线程。以下是Java中给多线程使用的一些主要方法:
-
继承
Thread
类:- 创建一个新的类继承自
Thread
类。 - 覆盖
run()
方法以定义线程的执行行为。 - 创建该类的实例,并调用其
start()
方法来启动线程。
- 创建一个新的类继承自
-
实现
Runnable
接口:- 创建一个类实现
Runnable
接口,并实现run()
方法。 - 创建
Thread
类的实例,将实现了Runnable
接口的类的实例作为参数传给Thread
的构造函数。 - 调用
Thread
实例的start()
方法来启动线程。
- 创建一个类实现
-
使用
Executor
框架:Executor
框架提供了一种高级的线程管理和任务调度机制。- 可以通过
Executors
类的静态工厂方法创建不同类型的线程池,如newFixedThreadPool
、newCachedThreadPool
等。 - 使用线程池,可以通过
execute()
方法提交实现了Runnable
接口的任务,或者通过submit()
方法提交实现了Callable
接口的任务。
-
使用
Future
和Callable
:Callable
接口类似于Runnable
,不同之处在于Callable
可以返回结果并能抛出异常。- 提交
Callable
任务给ExecutorService
后,会返回一个Future
对象,通过这个对象可以获取任务执行结果或取消任务。
-
使用
ForkJoin
框架:ForkJoin
框架专为大规模并行计算设计,它利用工作窃取算法来提高CPU的利用率。- 继承
RecursiveTask
(有返回值)或RecursiveAction
(无返回值),实现它们的compute
方法来定义分支和合并的逻辑。 - 创建
ForkJoinPool
实例并提交ForkJoinTask
。
以上是Java中实现多线程的一些常用方法及同步机制。根据具体的应用场景和需求,开发者可以选择适合的方法来实现并发编程。
1.Thread线程使用方法
Thread
是Java中实现多线程程序的基本单位。每个Thread
对象都代表了一个线程的实例。Java中的线程是通过java.lang.Thread
类来实现的。Thread
类本身实现了Runnable
接口,允许你覆盖其run()
方法,定义线程执行的任务。
Thread
类是实现线程的一种方式。每个Thread
对象代表一个线程的实例。- 通过继承
Thread
类并重写其run()
方法来创建一个新的线程。run()
方法定义了线程的执行行为。 - 创建
Thread
类的实例后,可以调用其start()
方法来启动线程,这会导致run()
方法的执行。
1.1.Thread的核心特性
- 简单性:直接继承
Thread
类并覆盖run()
方法,就可以定义线程的任务。 - 直接控制:由于是直接操作线程对象,可以很容易地管理和控制线程的状态(如开始、暂停、继续、中断)。
- 限制性:由于Java是单继承的,如果你的类已经继承了另一个类,就不能通过继承
Thread
类的方式来创建线程。
1.2.代码样例
public class MyThread extends Thread {
public MyThread(String threadName){
this.setName(threadName);
}
public void run() {
for (int i = 0; i < 20; i++) {
// 打印正在执行的线程名称
System.out.println(Thread.currentThread().getName() + " Value " + i);
}
}
}
public class ThreadExample {
public static void runDemo(){
// 创建线程
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
// 启动线程
t1.start();
t2.start();
}
}
public class DemoMain {
public static void main(String[] args) {
ThreadExample.runDemo();
}
}
- 输出结果
线程1 Value 0
线程2 Value 0
线程2 Value 1
线程2 Value 2
线程2 Value 3
线程2 Value 4
线程2 Value 5
线程1 Value 1
线程2 Value 6
线程1 Value 2
线程2 Value 7
线程1 Value 3
线程2 Value 8
线程1 Value 4
线程2 Value 9
线程1 Value 5
线程2 Value 10
线程1 Value 6
线程2 Value 11
线程1 Value 7
线程2 Value 12
线程1 Value 8
线程2 Value 13
线程1 Value 9
线程2 Value 14
线程1 Value 10
线程2 Value 15
线程1 Value 11
线程2 Value 16
线程1 Value 12
线程2 Value 17
线程1 Value 13
线程2 Value 18
线程1 Value 14
线程2 Value 19
线程1 Value 15
线程1 Value 16
线程1 Value 17
线程1 Value 18
线程1 Value 19
1.3.Thread的其他细节操作
1.3.1.等待线程结束(join
)
class ThreadJoinExample extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500); // 模拟执行任务
System.out.println(Thread.currentThread().getName() + " index=" + i);
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
}
}
public static void main(String[] args) {
ThreadJoinExample t1 = new ThreadJoinExample();
ThreadJoinExample t2 = new ThreadJoinExample();
t1.start();
try {
t1.join(); // 等待t1执行完成
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
1.3.2.中断线程(interrupt)
interrupt()
方法用于中断线程。如果线程因为调用Object.wait()
、Thread.join()
或Thread.sleep()
方法而被阻塞,它将抛出一个InterruptedException
。- 线程可以通过检查中断状态来优雅地终止执行。
class ThreadInterruptExample extends Thread {
public void run() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000); // 模拟执行任务
System.out.println(Thread.currentThread().getName() + " index=" + i);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " Interrupted");
}
}
public static void main(String[] args) {
ThreadInterruptExample t1 = new ThreadInterruptExample();
t1.start();
try {
Thread.sleep(3000); // 让t1有足够的时间运行
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt(); // 请求中断t1线程
}
}
1.3.3.设置和检查线程的优先级
- 线程的优先级通过整数表示,范围从
Thread.MIN_PRIORITY
(1)到Thread.MAX_PRIORITY
(10)。默认优先级为Thread.NORM_PRIORITY
(5)。 - 可以使用
setPriority(int)
方法来设置线程的优先级,并使用getPriority()
来检查优先级。
class ThreadPriorityExample extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + " Priority: " + Thread.currentThread().getPriority());
}
public static void main(String[] args) {
ThreadPriorityExample t1 = new ThreadPriorityExample();
ThreadPriorityExample t2 = new ThreadPriorityExample();
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
1.3.4.守护线程(Daemon Thread)
- 守护线程是一种在后台运行的线程,当所有的非守护线程都结束时,守护线程会自动终止。
- 可以通过
setDaemon(true)
方法将线程设置为守护线程,默认情况下,线程是非守护线程。
Thread daemonThread = new Thread(() -> {
while (true) {
// 执行后台任务
}
});
daemonThread.setDaemon(true); // 将线程设置为守护线程
daemonThread.start();
1.3.5.线程睡眠(Sleep)
Thread.sleep()
方法使当前线程暂停执行一段时间,以毫秒为单位。- 睡眠期间,线程不会释放锁,因此其他线程无法访问被当前线程持有的锁。
try {
Thread.sleep(1000); // 线程睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
1.3.6.线程等待(Wait)与唤醒(Notify)
wait()
方法使线程进入等待状态,直到其他线程调用相同对象上的notify()
或notifyAll()
方法唤醒它。- 必须在同步块中使用
wait()
和notify()
方法,否则会抛出IllegalMonitorStateException
异常。
synchronized (object) {
object.wait(); // 线程等待
}
synchronized (object) {
object.notify(); // 唤醒一个等待线程
// 或者使用 object.notifyAll(); 唤醒所有等待线程
}
1.3.7.注意事项
- 避免使用
Thread.stop()
:由于它是不安全的,Java已经弃用了stop()
方法来停止线程。它会导致线程立即终止,而不会进行任何清理操作,可能会产生不一致的状态。应该使用中断和检查机制来优雅地终止线程。 - 优先级只是给线程调度器一个提示:线程的优先级并不能保证执行顺序或者执行时间。线程调度器可能会忽略这些优先级,特别是在不同的操作系统上。
- 中断是一种协作机制:线程需要定期检查其中断状态,并相应地优雅地终止。
2.Runnable线程使用方法
2.1.Runnable的核心特性
Runnable
接口是Java中用于表示可运行任务的标准接口,它是实现多线程的一种方式。其核心特性如下:
-
抽象任务:
Runnable
接口定义了一个抽象方法run()
,用于表示具体的任务逻辑。实现Runnable
接口的类需要实现run()
方法,将具体的任务逻辑放在其中。 -
无返回值:
run()
方法没有返回值,它是一个void
类型的方法。因此,Runnable
接口表示的是一种无返回值的任务。 -
多线程支持:实现
Runnable
接口的类可以被多个线程共享,多个线程可以同时执行同一个Runnable
对象的run()
方法。 -
线程与任务解耦:
Runnable
接口将任务的定义与线程的实现解耦,使得任务可以在不同的线程中执行,从而提高了代码的灵活性和可复用性。 -
可与Thread类结合使用:
Runnable
接口通常与Thread
类结合使用,通过创建一个Thread
对象并将Runnable
对象作为参数传递给Thread
对象的构造方法,来创建并启动一个新线程。 -
适用性广泛:由于其简单、灵活的特性,
Runnable
接口适用于各种需要并发执行的任务,可以用于各种场景,包括但不限于并行计算、异步处理、任务调度等。
综上所述,Runnable
接口是实现多线程的一种基本方式,它定义了一个抽象的任务,并提供了一种通用的方式来表示可运行的任务,适用于各种需要并发执行的场景。
2.2. 代码样例
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " Value " + i);
}
}
}
public class RunnableExample {
public static void runDemo(){
// 创建Runnable实例
MyRunnable myRunnable = new MyRunnable();
// 创建Thread对象,并将Runnable实例作为目标对象传递给Thread构造器
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
// 启动线程
t1.start();
t2.start();
}
}
- 输出结果
Thread-0 Value 0
Thread-1 Value 0
Thread-0 Value 1
Thread-0 Value 2
Thread-0 Value 3
Thread-1 Value 1
Thread-1 Value 2
Thread-1 Value 3
Thread-1 Value 4
Thread-0 Value 4
Thread-0 Value 5
Thread-0 Value 6
Thread-0 Value 7
Thread-0 Value 8
Thread-0 Value 9
Thread-0 Value 10
Thread-0 Value 11
Thread-0 Value 12
Thread-0 Value 13
Thread-0 Value 14
Thread-0 Value 15
Thread-1 Value 5
Thread-0 Value 16
Thread-1 Value 6
Thread-0 Value 17
Thread-1 Value 7
Thread-0 Value 18
Thread-1 Value 8
Thread-0 Value 19
Thread-1 Value 9
Thread-1 Value 10
Thread-1 Value 11
Thread-1 Value 12
Thread-1 Value 13
Thread-1 Value 14
Thread-1 Value 15
Thread-1 Value 16
Thread-1 Value 17
Thread-1 Value 18
Thread-1 Value 19
3.Thread和Runnable 区别
Thread
和Runnable
是实现多线程程序的两种基本方式,它们各有特点和适用场景。以下是Thread
与Runnable
之间的主要区别:
3.1. 继承与实现的区别
- Thread:是一个类,继承
Thread
类来创建线程意味着你的线程类不能再继承其他类,因为Java不支持多重继承。 - Runnable:是一个接口,实现
Runnable
接口如果你的类已经继承了另一个类,你仍然可以通过实现Runnable
接口来创建线程。
3.2. 设计灵活性
- Thread:直接继承
Thread
类可能会限制类的扩展性,因为你的类已经继承了Thread
,它不能再继承其他类。 - Runnable:实现
Runnable
接口更加灵活,它允许你的类继承其他类,在多个线程间共享相同的资源。
3.3. 资源共享
- Thread:每个
Thread
类的实例都是一个单独的对象,它们之间的资源不共享,除非显式地使用外部资源。 - Runnable:多个线程可以共享同一个
Runnable
实例,因此它们可以访问相同的资源,这使得在多个线程之间共享数据和行为变得更加容易。
3.4. 适用场景
- Thread:当你的线程不需要访问共享资源或你只是创建少量线程时,继承
Thread
类可能是一个简单的选择。 - Runnable:当你需要多个线程共享数据或资源,或者你的类已经继承了另一个类时,实现
Runnable
接口是更好的选择。
3.5. 推荐做法
虽然两种方式都可以用来创建线程,但实践中更推荐实现Runnable
接口,原因包括:
- 实现
Runnable
接口的方式更加灵活,允许继承其他类并实现多个接口。 - 它支持多个线程共享同一个
Runnable
实例,便于线程间的资源共享。 - 与
Executor
框架结合使用时,通常要求提供Runnable
或Callable
任务,而不是Thread
对象。
总的来说,虽然直接使用Thread
类可以简化某些编程场景,但实现Runnable
接口提供了更高的灵活性和资源共享的能力,因此在多数情况下更被推荐。
4.Executor线程池的使用方法
Executor
是Java中用于执行任务的框架,它提供了一种将任务提交与任务执行分离的方式,可以更好地管理和控制线程的创建和执行。Executor
框架使得并发编程变得更加简单和可控,可以有效地利用系统资源,提高程序的性能和可扩展性。
4.1.Executor框架的核心组件
-
Executor接口:
Executor
是一个接口,它定义了单一的方法execute(Runnable command)
,用于执行给定的任务。 -
ExecutorService接口:
ExecutorService
是Executor
的子接口,它扩展了Executor
接口,提供了更丰富的功能。主要包括:- 提交任务并返回
Future
对象,用于追踪任务的执行状态和结果。 - 管理线程池的生命周期,包括启动、关闭、终止等。
- 提交任务并返回
-
ThreadPoolExecutor类:
ThreadPoolExecutor
是ExecutorService
接口的一个具体实现,它是一个灵活的线程池实现,可以根据需要自动调整线程数量。 -
Executors工厂类:
Executors
类是一个工厂类,提供了创建各种类型线程池的静态方法,如newFixedThreadPool
、newCachedThreadPool
、newSingleThreadExecutor
等。 -
ScheduledExecutorService接口:
ScheduledExecutorService
是ExecutorService
的一个子接口,它提供了可以延迟执行或定期执行任务的功能。
4.2.Executor框架的优点
- 简化并发编程:
Executor
框架将任务的提交和执行分离开来,简化了并发编程的复杂性。 - 提高资源利用率:通过使用线程池,可以重复利用已创建的线程,减少了线程的创建和销毁开销。
- 提高性能:通过并行执行多个任务,可以加快任务的执行速度,提高程序的性能。
- 提高可管理性:
Executor
框架提供了一系列方法来管理线程池的状态和行为,如监控线程池的执行状态、设置线程池的大小等。
4.3.代码样例
public class CustomThreadFactory implements ThreadFactory {
private String threadNamePrefix;
public CustomThreadFactory(String threadNamePrefix) {
this.threadNamePrefix = threadNamePrefix;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
// 设置线程名称
thread.setName(threadNamePrefix + "-" + thread.getId());
return thread;
}
}
public class ExecutorExample {
public static void runDemo() {
// 创建自定义的ThreadFactory
ThreadFactory threadFactory = new CustomThreadFactory("Xiaocai_CustomThread");
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5,threadFactory);
// 提交任务
for (Integer i = 0; i < 10; i++) {
final Integer threadIndex=i;
executor.execute(new MyThread(threadIndex.toString()));
}
System.out.println("关闭线程池之前");
// 关闭线程池
executor.shutdown();
System.out.println("关闭线程池之后");
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池中所有线程执行完毕");
}
}
- 输出结果
已连接到目标 VM, 地址: ''127.0.0.1:14181',传输: '套接字''
Xiaocai_CustomThread-12 Value 0
关闭线程池之前
Xiaocai_CustomThread-14 Value 0
Xiaocai_CustomThread-14 Value 1
Xiaocai_CustomThread-14 Value 2
Xiaocai_CustomThread-14 Value 3
Xiaocai_CustomThread-14 Value 4
Xiaocai_CustomThread-16 Value 0
Xiaocai_CustomThread-16 Value 1
Xiaocai_CustomThread-16 Value 2
Xiaocai_CustomThread-16 Value 3
Xiaocai_CustomThread-12 Value 1
Xiaocai_CustomThread-12 Value 2
Xiaocai_CustomThread-12 Value 3
Xiaocai_CustomThread-12 Value 4
Xiaocai_CustomThread-12 Value 5
Xiaocai_CustomThread-12 Value 6
关闭线程池之后
Xiaocai_CustomThread-16 Value 4
Xiaocai_CustomThread-14 Value 5
Xiaocai_CustomThread-14 Value 6
Xiaocai_CustomThread-16 Value 5
Xiaocai_CustomThread-16 Value 6
Xiaocai_CustomThread-16 Value 7
Xiaocai_CustomThread-16 Value 8
Xiaocai_CustomThread-16 Value 9
Xiaocai_CustomThread-18 Value 0
Xiaocai_CustomThread-20 Value 0
Xiaocai_CustomThread-20 Value 1
Xiaocai_CustomThread-12 Value 7
Xiaocai_CustomThread-20 Value 2
Xiaocai_CustomThread-18 Value 1
Xiaocai_CustomThread-16 Value 10
Xiaocai_CustomThread-16 Value 11
Xiaocai_CustomThread-14 Value 7
Xiaocai_CustomThread-14 Value 8
Xiaocai_CustomThread-14 Value 9
Xiaocai_CustomThread-16 Value 12
Xiaocai_CustomThread-18 Value 2
Xiaocai_CustomThread-18 Value 3
Xiaocai_CustomThread-18 Value 4
Xiaocai_CustomThread-20 Value 3
Xiaocai_CustomThread-12 Value 8
Xiaocai_CustomThread-20 Value 4
Xiaocai_CustomThread-18 Value 5
Xiaocai_CustomThread-16 Value 13
Xiaocai_CustomThread-14 Value 10
Xiaocai_CustomThread-16 Value 14
Xiaocai_CustomThread-18 Value 6
Xiaocai_CustomThread-20 Value 5
Xiaocai_CustomThread-12 Value 9
Xiaocai_CustomThread-20 Value 6
Xiaocai_CustomThread-18 Value 7
Xiaocai_CustomThread-16 Value 15
Xiaocai_CustomThread-14 Value 11
Xiaocai_CustomThread-16 Value 16
Xiaocai_CustomThread-18 Value 8
Xiaocai_CustomThread-20 Value 7
Xiaocai_CustomThread-12 Value 10
Xiaocai_CustomThread-20 Value 8
Xiaocai_CustomThread-18 Value 9
Xiaocai_CustomThread-16 Value 17
Xiaocai_CustomThread-14 Value 12
Xiaocai_CustomThread-16 Value 18
Xiaocai_CustomThread-18 Value 10
Xiaocai_CustomThread-20 Value 9
Xiaocai_CustomThread-12 Value 11
Xiaocai_CustomThread-20 Value 10
Xiaocai_CustomThread-18 Value 11
Xiaocai_CustomThread-16 Value 19
Xiaocai_CustomThread-14 Value 13
Xiaocai_CustomThread-18 Value 12
Xiaocai_CustomThread-20 Value 11
Xiaocai_CustomThread-12 Value 12
Xiaocai_CustomThread-20 Value 12
Xiaocai_CustomThread-18 Value 13
Xiaocai_CustomThread-16 Value 0
Xiaocai_CustomThread-14 Value 14
Xiaocai_CustomThread-16 Value 1
Xiaocai_CustomThread-18 Value 14
Xiaocai_CustomThread-20 Value 13
Xiaocai_CustomThread-12 Value 13
Xiaocai_CustomThread-20 Value 14
Xiaocai_CustomThread-18 Value 15
Xiaocai_CustomThread-16 Value 2
Xiaocai_CustomThread-14 Value 15
Xiaocai_CustomThread-16 Value 3
Xiaocai_CustomThread-18 Value 16
Xiaocai_CustomThread-20 Value 15
Xiaocai_CustomThread-12 Value 14
Xiaocai_CustomThread-20 Value 16
Xiaocai_CustomThread-18 Value 17
Xiaocai_CustomThread-16 Value 4
Xiaocai_CustomThread-14 Value 16
Xiaocai_CustomThread-16 Value 5
Xiaocai_CustomThread-18 Value 18
Xiaocai_CustomThread-20 Value 17
Xiaocai_CustomThread-12 Value 15
Xiaocai_CustomThread-20 Value 18
Xiaocai_CustomThread-18 Value 19
Xiaocai_CustomThread-16 Value 6
Xiaocai_CustomThread-14 Value 17
Xiaocai_CustomThread-16 Value 7
Xiaocai_CustomThread-18 Value 0
Xiaocai_CustomThread-20 Value 19
Xiaocai_CustomThread-12 Value 16
Xiaocai_CustomThread-20 Value 0
Xiaocai_CustomThread-18 Value 1
Xiaocai_CustomThread-16 Value 8
Xiaocai_CustomThread-14 Value 18
Xiaocai_CustomThread-16 Value 9
Xiaocai_CustomThread-18 Value 2
Xiaocai_CustomThread-20 Value 1
Xiaocai_CustomThread-12 Value 17
Xiaocai_CustomThread-20 Value 2
Xiaocai_CustomThread-18 Value 3
Xiaocai_CustomThread-16 Value 10
Xiaocai_CustomThread-14 Value 19
Xiaocai_CustomThread-16 Value 11
Xiaocai_CustomThread-18 Value 4
Xiaocai_CustomThread-20 Value 3
Xiaocai_CustomThread-12 Value 18
Xiaocai_CustomThread-20 Value 4
Xiaocai_CustomThread-18 Value 5
Xiaocai_CustomThread-16 Value 12
Xiaocai_CustomThread-14 Value 0
Xiaocai_CustomThread-16 Value 13
Xiaocai_CustomThread-18 Value 6
Xiaocai_CustomThread-20 Value 5
Xiaocai_CustomThread-12 Value 19
Xiaocai_CustomThread-12 Value 0
Xiaocai_CustomThread-20 Value 6
Xiaocai_CustomThread-18 Value 7
Xiaocai_CustomThread-16 Value 14
Xiaocai_CustomThread-14 Value 1
Xiaocai_CustomThread-16 Value 15
Xiaocai_CustomThread-18 Value 8
Xiaocai_CustomThread-20 Value 7
Xiaocai_CustomThread-12 Value 1
Xiaocai_CustomThread-20 Value 8
Xiaocai_CustomThread-18 Value 9
Xiaocai_CustomThread-16 Value 16
Xiaocai_CustomThread-14 Value 2
Xiaocai_CustomThread-16 Value 17
Xiaocai_CustomThread-18 Value 10
Xiaocai_CustomThread-20 Value 9
Xiaocai_CustomThread-12 Value 2
Xiaocai_CustomThread-20 Value 10
Xiaocai_CustomThread-18 Value 11
Xiaocai_CustomThread-16 Value 18
Xiaocai_CustomThread-14 Value 3
Xiaocai_CustomThread-16 Value 19
Xiaocai_CustomThread-18 Value 12
Xiaocai_CustomThread-20 Value 11
Xiaocai_CustomThread-12 Value 3
Xiaocai_CustomThread-20 Value 12
Xiaocai_CustomThread-18 Value 13
Xiaocai_CustomThread-14 Value 4
Xiaocai_CustomThread-18 Value 14
Xiaocai_CustomThread-20 Value 13
Xiaocai_CustomThread-12 Value 4
Xiaocai_CustomThread-20 Value 14
Xiaocai_CustomThread-18 Value 15
Xiaocai_CustomThread-14 Value 5
Xiaocai_CustomThread-18 Value 16
Xiaocai_CustomThread-20 Value 15
Xiaocai_CustomThread-12 Value 5
Xiaocai_CustomThread-20 Value 16
Xiaocai_CustomThread-18 Value 17
Xiaocai_CustomThread-14 Value 6
Xiaocai_CustomThread-18 Value 18
Xiaocai_CustomThread-20 Value 17
Xiaocai_CustomThread-12 Value 6
Xiaocai_CustomThread-20 Value 18
Xiaocai_CustomThread-18 Value 19
Xiaocai_CustomThread-14 Value 7
Xiaocai_CustomThread-20 Value 19
Xiaocai_CustomThread-12 Value 7
Xiaocai_CustomThread-12 Value 8
Xiaocai_CustomThread-14 Value 8
Xiaocai_CustomThread-12 Value 9
Xiaocai_CustomThread-14 Value 9
Xiaocai_CustomThread-12 Value 10
Xiaocai_CustomThread-14 Value 10
Xiaocai_CustomThread-12 Value 11
Xiaocai_CustomThread-14 Value 11
Xiaocai_CustomThread-12 Value 12
Xiaocai_CustomThread-14 Value 12
Xiaocai_CustomThread-12 Value 13
Xiaocai_CustomThread-14 Value 13
Xiaocai_CustomThread-12 Value 14
Xiaocai_CustomThread-14 Value 14
Xiaocai_CustomThread-12 Value 15
Xiaocai_CustomThread-14 Value 15
Xiaocai_CustomThread-12 Value 16
Xiaocai_CustomThread-14 Value 16
Xiaocai_CustomThread-12 Value 17
Xiaocai_CustomThread-14 Value 17
Xiaocai_CustomThread-12 Value 18
Xiaocai_CustomThread-14 Value 18
Xiaocai_CustomThread-12 Value 19
Xiaocai_CustomThread-14 Value 19
线程池中所有线程执行完毕
与目标 VM 断开连接, 地址为: ''127.0.0.1:14181',传输: '套接字''
进程已结束,退出代码为 0
在上面的示例中,通过Executors.newFixedThreadPool(5)
方法创建了一个固定大小为5的线程池,然后提交了10个任务。线程池会自动管理这些任务的执行,使用合适数量的线程执行任务。最后通过executor.shutdown()
方法关闭线程池。
总之,Executor
框架提供了一种高级的、灵活的方式来执行任务,是Java并发编程中的重要工具之一,值得深入学习和应用。
4.4.细节操作
4.4.1.怎么判断线程池中所有线程全部运行完毕呢?
System.out.println("关闭线程池之前");
// 关闭线程池
executor.shutdown();
System.out.println("关闭线程池之后");
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池中所有线程执行完毕");
4.4.2.调用executor.shutdown()
的目的
调用executor.shutdown()
方法的目的是关闭线程池,即停止接受新的任务,并尝试将已提交但尚未开始执行的任务从任务队列中移除,同时等待已经提交的任务执行完毕。主要原因包括:
-
释放资源:线程池在执行完所有任务后,会保持活动状态,并且等待新的任务。如果不关闭线程池,线程池中的线程将一直存在,占用系统资源(如内存等),可能导致资源浪费。
-
避免任务丢失:如果不关闭线程池,那么主程序执行完毕后可能会立即结束,导致尚未执行完的任务丢失。
-
优雅关闭:调用
shutdown()
方法后,线程池不会立即关闭,而是等待之前提交的任务执行完成后关闭,保证已提交的任务能够被执行完毕,避免任务中途被打断。 -
避免线程泄露:如果不关闭线程池,线程池中的线程可能会一直存在,即使主程序已经执行完毕,导致线程泄露问题。
因此,一般情况下,在不再需要线程池时应该及时调用shutdown()
方法来关闭线程池,以释放资源并确保已提交的任务能够正常执行完毕。
5.Future使用方法
Future
是Java中表示异步计算结果的接口,它提供了一种机制,可以在任务执行完成之前先提交任务,并且在需要结果时获取任务执行的结果。通过Future
,可以在任务执行的过程中进行其他操作,而不必阻塞等待任务执行完成。
5.1.Future的核心特性
-
表示异步计算结果:
Future
接口表示一个异步计算的结果。可以通过get()
方法来获取计算的结果,如果计算尚未完成,则会阻塞直到计算完成。 -
取消任务:
Future
提供了cancel()
方法用于取消任务的执行。如果任务已经完成,或者由于其他原因无法取消,则cancel()
方法返回false
。 -
判断任务状态:
isDone()
方法用于判断任务是否完成,isCancelled()
方法用于判断任务是否被取消。
5.2.代码样例
public class FutureExample {
public static void runDemo() {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交任务并获取Future对象
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 模拟一个耗时的任务
Thread.sleep(2000);
return 42;
}
});
// 其他操作...
System.out.println("Do something else while waiting for the result...");
try {
// 获取任务结果,如果任务尚未完成,会阻塞直到任务完成
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
}
}
6.Callable使用方法
Callable
是Java中用于表示可调用任务的接口,与Runnable
接口类似,但它可以返回结果并且能够抛出异常。通常与ExecutorService
一起使用,通过submit(Callable)
方法提交Callable
任务,并返回一个Future
对象,用于获取任务的执行结果。
6.1.Callable的核心特性
-
返回结果:
Callable
接口使用泛型来定义返回类型,可以通过call()
方法返回计算的结果。 -
抛出异常:
call()
方法可以抛出异常,允许任务执行过程中发生异常情况。
6.2.使用示例
public class CallableExample {
public static void runDemo() {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交Callable任务并获取Future对象
Future<Integer> future = executor.submit(new MyCallable());
// 其他操作...
System.out.println("Do something else while waiting for the result...");
try {
// 获取任务结果,如果任务尚未完成,会阻塞直到任务完成
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 模拟一个耗时的任务
Thread.sleep(2000);
return 42;
}
}
在上面的示例中,首先创建了一个固定大小为1的线程池。然后通过executor.submit(Callable)
方法提交一个Callable
任务,并获得一个Future
对象。在任务执行的过程中,可以执行其他操作,而不必等待任务完成。当需要任务结果时,调用future.get()
方法,如果任务已经完成,则立即返回结果;如果任务尚未完成,则会阻塞直到任务完成,并返回结果。
总之,Callable
接口允许在任务执行过程中返回结果和抛出异常,提供了一种更加灵活和功能丰富的任务定义方式,适用于需要异步计算结果的场景。
7.ForkJoin使用方法
ForkJoin
框架是Java中用于并行计算的框架,它基于"分而治之"的思想,能够有效地利用多核处理器的优势,加速任务的执行速度。ForkJoin
框架提供了ForkJoinPool
、ForkJoinTask
和RecursiveTask
等类来支持并行计算。
ForkJoin框架的核心特性
-
任务分解:
ForkJoin
框架通过递归的方式将大任务划分为小任务,然后并行执行这些小任务,从而实现任务的分解和并行处理。 -
工作窃取:
ForkJoin
框架中的ForkJoinPool
使用工作窃取算法来管理线程池中的工作线程。当一个工作线程没有任务可执行时,它会从其他工作线程的任务队列中偷取任务来执行,从而实现负载均衡。 -
递归任务:
ForkJoinTask
是ForkJoin
框架中表示任务的抽象类,它有两个主要子类:RecursiveAction
和RecursiveTask
,分别用于没有返回值的任务和有返回值的任务。
使用示例
下面是一个使用ForkJoin
框架计算数组元素之和的示例:
public class ForkJoinExample {
public static void runDemo() {
// 创建ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
// 创建一个大数组
int[] array = new int[1000];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
// 创建ForkJoin任务
ForkJoinTask<Integer> task = new SumTask(array, 0, array.length);
// 提交任务并获取结果
Integer result = pool.invoke(task);
// 输出结果
System.out.println("Sum of array elements: " + result);
// 关闭线程池
pool.shutdown();
}
}
class SumTask extends RecursiveTask<Integer> {
private int[] array;
private int start;
private int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) {
// 如果任务规模小于等于10,直接计算结果
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// 否则将任务分解成两个子任务
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
// 并行执行子任务
leftTask.fork();
rightTask.fork();
// 合并子任务的结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 返回结果
return leftResult + rightResult;
}
}
}
在上面的示例中,首先创建了一个ForkJoinPool
,然后创建了一个包含1000个元素的数组。接着创建了一个ForkJoinTask
(这里使用了RecursiveTask
的子类SumTask
),并将其提交给ForkJoinPool
进行执行。SumTask
任务负责计算数组的元素之和,如果任务规模较小,则直接计算结果;否则将任务分解成两个子任务,分别计算子数组的元素之和,然后合并结果。
总之,ForkJoin
框架通过任务的分解和并行执行,能够提高任务的执行效率,特别适用于处理大规模的数据并且任务之间存在递归关系的场景。
8.对比一下Thread、Runnable、Executor、Future、Callable、ForkJoin这几种线程操作的方式,对比他们之间的异同点,以及使用场景
下面是对Thread、Runnable、Executor、Future、Callable和ForkJoin这几种线程操作方式的异同点以及使用场景的对比:
8.1. Thread
- 方式:直接继承
Thread
类,覆写run()
方法。 - 异同点:与其他方式相比,使用起来更为简单直观,但线程和任务的耦合性较强,不利于任务的复用和资源的管理。
- 使用场景:适用于简单的并发任务,不需要共享资源或状态的场景。
8.2. Runnable
- 方式:实现
Runnable
接口,实现run()
方法。 - 异同点:相比于直接继承
Thread
类,实现Runnable
接口的方式更为灵活,可以更好地支持任务的复用和资源的管理。 - 使用场景:适用于需要共享资源或状态的场景,也适用于已经继承其他类的情况下,无法再继承
Thread
类。
8.3. Executor
- 方式:通过
Executor
框架来管理和执行任务。 - 异同点:相比于直接创建线程,使用
Executor
框架能够更好地管理线程资源,提高线程的复用性和可管理性。 - 使用场景:适用于需要管理和调度多个任务的场景,例如批量处理任务、任务队列等。
8.4. Future
- 方式:通过
Future
接口来获取异步计算的结果。 - 异同点:
Future
接口提供了异步计算结果的获取方式,允许任务的提交和获取结果的操作分离开来,提高了程序的并发性能。 - 使用场景:适用于需要等待异步任务完成并获取结果的场景,例如并行计算、I/O操作等。
8.5. Callable
- 方式:实现
Callable
接口,实现call()
方法。 - 异同点:与
Runnable
接口类似,但Callable
接口允许任务返回结果和抛出异常,功能更为丰富。 - 使用场景:适用于需要任务返回结果或抛出异常的场景,例如需要等待任务执行完成并获取结果的场景。
8.6. ForkJoin
- 方式:通过
ForkJoin
框架实现任务的并行计算。 - 异同点:相比于传统的线程池,
ForkJoin
框架提供了任务分解和工作窃取等机制,能够更有效地利用多核处理器的优势。 - 使用场景:适用于需要大规模并行计算的场景,例如递归任务的分解、归并排序等。
综上所述,不同的线程操作方式有不同的特点和适用场景。直接使用Thread
和Runnable
接口适合简单的并发任务;Executor
框架适合管理和调度多个任务的场景;Future
和Callable
接口适合需要等待任务完成并获取结果的场景;ForkJoin
框架适合需要大规模并行计算的场景。在实际应用中,根据具体的需求选择合适的线程操作方式能够更好地提高程序的性能和可维护性。