Java多线程创建方式全解
在Java编程中,多线程是一个重要的概念,它能让程序同时执行多个任务,提高程序的效率和响应能力。以下将详细介绍Java中创建多线程的四种方式。
一、继承Thread类
(一)创建步骤
- 定义线程类
创建一个类并继承自Thread
类。在这个类中,重写run()
方法,run()
方法中的代码就是这个线程要执行的任务。例如:
class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("线程 " + getName() + " 执行,i = " + i);}}
}
- 创建并启动线程
在主程序中创建线程对象,并调用start()
方法来启动线程。start()
方法会自动调用线程对象的run()
方法,使得线程开始执行。例如:
public class ThreadExample {public static void main(String[] args) {MyThread thread1 = new MyThread();thread1.start();MyThread thread2 = new MyThread();thread2.start();}
}
(二)特点
这种方式简单直接,符合面向对象的编程思想。但是,由于Java是单继承的,如果一个类已经继承了其他类,就无法再继承Thread
类来创建线程。
二、实现Runnable接口
(一)创建步骤
- 定义实现Runnable接口的类
创建一个类实现Runnable
接口,然后实现接口中的run()
方法,将线程要执行的任务写在run()
方法中。例如:
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("线程 " + Thread.currentThread().getName() + " 执行,i = " + i);}}
}
- 创建并启动线程
首先创建实现了Runnable
接口的类的对象,然后将这个对象作为参数传递给Thread
类的构造函数来创建线程对象,最后调用start()
方法启动线程。例如:
public class RunnableExample {public static void main(String[] args) {MyRunnable runnable1 = new MyRunnable();Thread thread1 = new Thread(runnable1);thread1.start();MyRunnable runnable2 = new MyRunnable();Thread thread2 = new Thread(runnable2);thread2.start();}
}
(二)特点
这种方式避免了单继承的限制,因为一个类可以实现多个接口。同时,多个线程可以共享同一个Runnable
对象,比较适合多个线程执行相同任务的情况。
三、使用Callable和Future接口(适用于有返回值的线程)
(一)创建步骤
- 定义实现Callable接口的类
创建一个类实现Callable
接口,需要指定返回值类型。实现call()
方法,在这个方法中编写线程要执行的任务,并返回一个结果。例如,计算1到100的整数和:
import java.util.concurrent.Callable;class MyCallable implements Callable<Integer> {@Overridepublic Integer call() {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}return sum;}
}
- 创建并执行线程,获取结果
通过ExecutorService
来管理线程的执行。首先创建Callable
对象,然后将其提交给ExecutorService
,返回一个Future
对象。通过Future
对象可以获取线程执行后的返回值。例如:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableExample {public static void main(String[] args) {MyCallable callable = new MyCallable();// 创建一个线程池,这里只使用一个线程来执行任务,实际可以根据需求调整线程数量ExecutorService executor = Executors.newSingleThreadExecutor();try {Future<Integer> future = executor.submit(callable);// 获取线程执行后的结果Integer result = future.get();System.out.println("计算结果为: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();} finally {// 关闭线程池executor.shutdown();}}
}
(二)特点
这种方式的优势在于可以获取线程执行后的返回值,并且可以通过ExecutorService
来更好地管理线程,如设置线程池大小、控制线程的执行顺序等。但相比前两种方式,代码稍微复杂一些。
四、使用线程池创建多线程
(一)使用ExecutorService
和Executors
创建线程池
- 创建线程池
可以使用Executors
工厂类来创建不同类型的线程池。例如,创建一个固定大小的线程池:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个包含5个线程的固定大小线程池ExecutorService executorService = Executors.newFixedThreadPool(5);}
}
Executors
还可以创建其他类型的线程池,如CachedThreadPool
(根据需要创建线程,线程空闲一段时间后会被回收)、SingleThreadExecutor
(只有一个线程的线程池)等。
2. 定义任务(可以是实现Runnable
接口或Callable
接口的类)
例如,定义一个实现Runnable
接口的任务类:
class MyTask implements Runnable {@Overridepublic void run() {System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务");}
}
- 提交任务到线程池
将任务提交给线程池执行。对于实现Runnable
接口的任务,可以使用execute()
方法提交;对于实现Callable
接口的任务(用于有返回值的情况),可以使用submit()
方法提交。以下是提交Runnable
任务的示例:
public class ThreadPoolExample {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);// 创建任务对象MyTask task = new MyTask();// 提交任务到线程池,这个任务会被线程池中的某个线程执行for (int i = 0; i < 10; i++) {executorService.execute(task);}// 关闭线程池,不再接受新任务,但会等待已提交的任务执行完毕executorService.shutdown();}
}
- 关闭线程池(可选)
当任务都提交完毕后,可以关闭线程池。shutdown()
方法会平滑地关闭线程池,它会等待所有已提交的任务执行完毕后再关闭线程池。如果希望立即关闭线程池,不等待未完成的任务,可以使用shutdownNow()
方法,但这种方式可能会导致正在执行的任务被中断。
(二)自定义线程池(使用ThreadPoolExecutor
)
Executors
工厂类提供的创建线程池的方法实际上是对ThreadPoolExecutor
的封装。如果需要更精细地控制线程池的参数和行为,可以直接使用ThreadPoolExecutor
来创建线程池。
ThreadPoolExecutor
的构造函数有多个参数,其主要参数如下:
corePoolSize
:核心线程数,即使线程池中的线程处于空闲状态,也会保留这些线程。maximumPoolSize
:线程池允许的最大线程数。keepAliveTime
:当线程数大于核心线程数时,多余线程的空闲存活时间。unit
:keepAliveTime
的时间单位。workQueue
:用于存放等待执行任务的阻塞队列。threadFactory
:用于创建新线程的工厂。handler
:当线程池和队列都满了时,用于处理新任务的拒绝策略。
以下是一个自定义线程池的示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class CustomThreadPoolExample {public static void main(String[] args) {// 核心线程数为3int corePoolSize = 3;// 最大线程数为5int maximumPoolSize = 5;// 多余线程空闲存活时间为10秒long keepAliveTime = 10;TimeUnit unit = TimeUnit.SECONDS;// 创建阻塞队列用于存放任务BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,keepAliveTime, unit, workQueue);MyTask task = new MyTask();for (int i = 0; i < 10; i++) {try {threadPoolExecutor.execute(task);} catch (Exception e) {e.printStackTrace();}}// 关闭线程池threadPoolExecutor.shutdown();}
}
通过自定义线程池,可以根据具体的应用场景和性能要求,精确地调整线程池的参数,从而更好地控制线程的执行和资源的利用。