JAVA多线程实现方式
JAVA多线程实现方式主要有四种:
继承Thread类、
实现Runnable接口、
Callable、Future实现有返回结果的多线程。
使用ExecutorService
一、继承Thread类
启动线程的唯一方法就是通过Thread类的start()实例方法。
start()方法是一个native方法,它将启动一个新线程,并执行run()方法。
/**
* @author Myli
* @create 2023-02-19 5:51
*/
public class testmyli {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.start();
}
}
class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
Thread本质上也是实现了Runnable接口的一个实例,
二、实现Runnable接口
众所周知,java是单继承的,如果某个类他已经有父类了,就无法直接extends Thread,此时,如果要实现多线程,可以通过实现一个Runnable接口来进行
/**
* @author Myli
* @create 2023-02-21 0:27
*/
public class testrunable {
public static void main(String[] args) {
//new一个MyThread,
MyThread myThread = new MyThread();
//new一个Thread
Thread thread = new Thread(myThread);
thread.start();
}
static class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
}
三、 实现callable接口
/**
* @author Myli
* @create 2023-02-21 0:44
*/
public class testcallable {
public static void main(String[] args) {
//调用callable 可以有返回值 可以捕获异常
Callable<String> tc = new TestCallable();
FutureTask<String> task = new FutureTask<String>(tc);
new Thread(task).start();
try {
System.out.println(task.get());//获取返回值
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
static class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("实现callable:" + Thread.currentThread().getName());
return "callable返回";
}
}
}
四、线程池
常见的有
定长线程池(FixedThreadPool)
定时线程池(ScheduledThreadPool )
可缓存线程池(CachedThreadPool)
单线程化线程池(SingleThreadExecutor)
完全手动的 (ThreadPoolExecutor)
定长线程池(FixedThreadPool)
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);
定时线程池(ScheduledThreadPool)
可缓存线程池(CachedThreadPool)
单线程化线程池(SingleThreadExecutor)
(ThreadPoolExecutor)
Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
其实 Executors 的 4 个功能线程有如下弊端:
FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,
可能会耗费非常大的内存,甚至 OOM。
CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,
可能会创建数量非常多的线程,甚至 OOM。
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行的任务
}
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。
创建线程对比
1.实现Runnable或Callable接口比继承Thread类的优势
(1)Runnable或Callable接口适合多个线程进行资源共享
(2)java中单一继承,但是多接口实现,提高扩展性
(3)增加程序的健壮性,代码和数据独立
(4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类
Callable和Runnable之间的区别
(1) Callable重写的是call()方法,Runnable重写的方法是run()方法
(2) call()方法执行后可以有返回值,run()方法没有返回值
(3) call()方法可以抛出异常,run()方法不可以
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果
参考
链接: Java 多线程:彻底搞懂线程池
链接: 创建线程的三种方式及区别
部分参考已找不到原出处