Java多线程创建方式和线程池的使用方法
前言
项目中涉及一些线程使用的改造,在使用修改线程的过程中,踩了不少坑.在此归纳总结一下,方便日后避开这些坑.
java使用线程的方法
1.继承Thread类
2.实现Runnable接口
3.使用ExecutorService、Callable、Future实现
其中 1和 2两种方式创建的线程 执行结束都没有返回值,
执行 run方法不可以抛出异常
3是创建线程 有返回值、可以抛出异常.
1.继承Thread类
代码示例:
package com.fl.thread; public class TestThread extends Thread{ public void run(){ super.run(); System.out.println("子线程执行了--------"); } } public class Test { public static void main(String[] args) { TestThread testThread = new TestThread(); testThread.start(); System.out.println("主线程执行----------"); } }
这种线程的使用方式 本质上其实 也是实现Runnable接口的
一个实例,它代表一个线程的实例,启动线程的唯一方法是
通过Thread 类的start 方法,start 方法时一个原生方法,
它会启动一个新线程,并且执行run()方法. 这种的局限性是 使用继承 Thread 类的方式创建线程时,是不支持多继承.
为了实现多继承要实现Runnable 接口.
执行的结果:
注意: 在使用多线程时,运行结果与调用顺序是无关的(这里的结果不代表线程的执行顺序,线程是并发执行的,如果多运行几次,打印顺序可能会不一样。多线程的运行过程中,CPU是以不确定的方式去执行线程的,故运行结果与代码的执行顺序或者调用顺序无关)。
调用run()方法 知识普通的方法调用(main方法中应该调用的是myThread的start方法,而不是run()方法。调用start()方法是告诉CPU此线程已经准备就绪可以执行,进而系统有时间就会来执行其run()方法。而直接调用run()方法,则不是异步执行,而是等同于调用函数般按顺序同步执行,这就失去了多线程的意义了。
),不会启动线程.如果多次
调用 start 方法,会抛出 IllegalThreadStateException
异常.
2.实现Runnable 接口
这种方法用的比较多,就是把继承Thread类改为实现Runnable接口.
代码示例:
package com.fl.thread; public class MyRunnable implements Runnable{ public void run() { System.out.println("执行子线程------"); } } //测试 public class TestRunnable { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); System.out.println("主线程运行结束--------"); } }
运行结果:
这里我们可看出 main中可以看到真正创建新线程还是通过Thread创建:
Thread thread = new Thread(runnable);
这一步Thread类的作用就是把run()方法包装成线程执行体,然后依然通过start去告诉系统这个线程已经准备好了可以安排执行。
3.使用ExecutorService、Callable、Future实现
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类. 要了解这框架 大家可以看下并发编程 的书籍.
实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread.
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕.
代码示例:
package com.fl.thread; import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { public Integer call() throws Exception { System.out.println("获取当前线程的名称"+Thread.currentThread().getName()+ "当前线程的Id"+Thread.currentThread().getName()); int count = 0; for (int i = 0; i <= 200000; i++) { count += i; } Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()+"end---"); return count; } } //测试 public class TestMyCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { // 将Callable包装成FutureTask,FutureTask也是一种Runnable MyCallable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<Integer>(callable); new Thread(futureTask).start(); // get方法会阻塞调用的线程 Integer sum = futureTask.get(); System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum); } }
Callable它也是一种函数式接口:
@FunctionalInterface
public interface Callable<V> { V call() throws Exception; }
上边测试中用到了 FutureTask
它的用法
public class FutureTask<V> implements RunnableFuture<V> { // 构造函数 public FutureTask(Callable<V> callable); // 取消线程 public boolean cancel(boolean mayInterruptIfRunning); // 判断线程 public boolean isDone(); // 获取线程执行结果 public V get() throws InterruptedException, ExecutionException; }
FutureTask 实现 RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
比较 总结一下 这三种方式
Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
Runnable: 实现接口,比Thread类更加灵活,
没有单继承的限制
Callable: Thread和Runnable都是重写的run()方法
并且没有返回值,Callable是重写的call()方法并且
有返回值并可以借助FutureTask类来判断线程是否已经
执行完毕或者取消线程执行
当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到
Thread类中,一般通过Thread类来启动线程
Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,
RunnableFuture继承Runnable,所以Callable
也算是一种Runnable,
所以三种实现方式本质上都是Runnable实现
关系 ExecutorService 用法
ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
所有的线程池 最终都是通过这方法来创建的.
详细的可见
https://blog.csdn.net/weixin_43975771/article/details/108034443
上一篇: 黑花生的产地?怎么保存花生?
下一篇: Netty学习笔记 3.8 选择器