手段(Thread、Callable、Executors)"/>
Java——常见的创建编程的手段(Thread、Callable、Executors)
1、创建线程有哪几种方式?
- 继承
Thread
类; - 实现
Runnable
接口; - 实现
Callable
接口; - 使用
Executors
工具类创建线程池。
1.1、继承Thread
类
1.1.1、步骤
- 定义一个
Thread
类的子类,重写run
方法,将相关逻辑实现,run()
方法就是线程要执行的业务逻辑方法; - 创建自定义的线程子类对象;
- 调用子类实例的
star()
方法来启动线程。
1.1.2、代码
public class MyThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");}
}
public class TheadTest {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();System.out.println(Thread.currentThread().getName() + " main()方法执行结束");}
}
1.1.3、执行结果
main main()方法执行结束
Thread-0 run()方法正在执行...
1.2、实现Runnable
接口
1.2.1、步骤
- 定义
Runnable
接口实现类MyRunnable
,并重写run()
方法; - 创建
MyRunnable
实例myRunnable
,以myRunnable
作为target
创建Thread
对象,该Thread
对象才是真正的线程对象; - 调用线程对象的
start()
方法。
1.2.2、代码
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " run()方法执行中...");}
}
public class RunnableTest {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();System.out.println(Thread.currentThread().getName() + " main()方法执行完成");}
}
1.2.3、执行结果
main main()方法执行完成
Thread-0 run()方法执行中...
1.3、实现 Callable
接口
1.3.1、步骤
- 创建实现
Callable
接口的类myCallable
- 以
myCallable
为参数创建FutureTask
对象 - 将
FutureTask
作为参数创建Thread
对象 - 调用线程对象的
start()
方法
1.3.2、代码
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() {System.out.println(Thread.currentThread().getName() + " call()方法执行中...");return 1;}
}public class CallableTest {public static void main(String[] args) {FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());Thread thread = new Thread(futureTask);thread.start();try {Thread.sleep(1000);System.out.println("返回结果 " + futureTask.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " main()方法执行完成");}
}
1.3.3、执行结果
Thread-0 call()方法执行中...
返回结果 1
main main()方法执行完成
1.4、使用Executors
工具类创建线程池
答:Executors
提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService
接口。主要有newFixedThreadPool
,newCachedThreadPool
,newSingleThreadExecutor
,newScheduledThreadPool
。
1.4.1、代码
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " run()方法执行中...");}
}
public class SingleThreadExecutorTest {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();MyRunnable runnableTest = new MyRunnable();for (int i = 0; i < 5; i++) {executorService.execute(runnableTest);}System.out.println("线程任务开始执行");executorService.shutdown();}
}
1.4.2、执行结果
线程任务开始执行
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
2、说一下 runnable
和 callable
有什么区别?
2.1、相同点
- 都是接口;
- 都可以编写多线程程序;
- 都采用
Thread.start()
启动线程;
2.2、不同点
Runnable
接口run()
方法无返回值;Callable
接口call
方法有返回值,是个泛型,和Future、FutureTask
配合可以用来获取异步执行的结果;Runnable
接口run()
方法只能抛出运行时异常,且无法捕获处理;Callable
接口call
方法允许抛出异常,可以获取异常信息;
Callalbe
接口支持返回执行结果,需要调用FutureTask.get()
得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
3、线程的 run()
和 start()
有什么区别?
- 每个线程都是通过某个特定
Thread
对象所对应的方法run()
来完成其操作的,run()
方法称为线程体。通过调用Thread
类的start()
方法来启动一个线程。 start()
方法用于启动线程,run()
方法用于执行线程的运行时代码。run()
可以重复调用,而start()
只能调用一次。start()
方法来启动一个线程,真正实现了多线程运行。调用start()
方法无需等待run()
方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread
类调用方法run()
来完成其运行状态,run()
方法运行结束, 此线程终止。然后**CPU
**再调度其它线程。run()
方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run()
,其实就相当于是调用了一个普通函数而已,直接待用run()
方法必须等待run()
方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()
方法而不是run()
方法。
4、为什么我们调用 start()
方法时会执行run()
方法,为什么我们不能直接调用run()
方法?
new
一个Thread
,线程进入了新建状态。调用start()
方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start()
会执行线程的相应准备工作,然后自动执行run()
方法的内容,这是真正的多线程工作。- 而直接执行
run()
方法,会把run
方法当成一个main
线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用
start
方法方可启动线程并使线程进入就绪状态,而run
方法只是thread
的一个普通方法调用,还是在主线程里执行。
5、什么是 Callable
和 Future
?
Callable
接口类似于Runnable
,从名字就可以看出来了,但是Runnable
不会返回结果,并且无法抛出返回结果的异常,而Callable
功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future
拿到,也就是说,Future
可以拿到异步执行任务的返回值。Future
接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说Callable
用于产生结果,Future
用于获取结果。
6、什么是 FutureTask
?
答:FutureTask
表示一个异步运算的任务。FutureTask
里面可以传入一个 Callable
的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask
对象可以对调用了 Callable
和 Runnable
的对象进行包装,由于 FutureTask
也是Runnable
接口的实现类,所以 FutureTask
也可以放入线程池中。
7、Future
和FutureTask
的区别
/**Future的使用方法**/ExecutorService service = Executors.newSingleThreadExecutor();Future<String> future = service.submit(new Callable<String>() {@Overridepublic String call() throws Exception {return "say helloWorld!!!";}});System.out.println(future.get());// 通过get返回结果/**FutureTask**/
ExecutorService service = Executors.newSingleThreadExecutor();
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {@Overridepublic String call() throws Exception {return "futureTask say HelloWorld!!!";}
});
service.execute(futureTask);
System.out.println(futureTask.get());
7、常见的线程池及使用场景
newSingleThreadExecutor
: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO
,LIFO
,优先级)执行。newFixedThreadPool
: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newCachedThreadPool
: 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newScheduledThreadPool
: 创建一个周期性线程池,支持定时及周期性任务执行。
8、线程池的核心参数
corePoolSize
: 表示当前线程池的核心线程数大小,即最小线程数(初始化线程数),线程池会维护当前数据的线程在线程池中,即使这些线程一直处于闲置状态,也不会被销毁;maximumPoolSize
: 表示线程池中允许的最大线程数;后文中会详细讲解keepAliveTime
: 表示空闲线程的存活时间,当线程池中的线程数量大于核心线程数且线程处于空闲状态,那么在指定时间后,这个空闲线程将会被销毁,从而逐渐恢复到稳定的核心线程数数量;unit
: 当前unit
表示的是keepAliveTime
存活时间的计量单位,通常使用TimeUnit.SECONDS
秒级;workQueue
: 任务工作队列;后文会结合maximumPoolSize
一块来讲threadFactory
: 线程工厂,用于创建新线程以及为线程起名字等handler
: 拒绝策略,即当任务过多无法及时处理时所需采取的策略;AbortPolicy
:丢弃任务并抛出RejectedExecutionException
异常。(默认这种)DiscardPolicy
:也是丢弃任务,但是不抛出异常DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)CallerRunsPolicy
:调用线程处理该任务,即创建了线程池的线程来执行被拒绝的任务
9、初始化线程池时线程数的选择
IO
密集型: 一般线程数需要设置2倍CPU
数以上,以此来尽量利用CPU
资源。CPU
密集型: 一般线程数量只需要设置CPU
数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU
利用率。
10、线程池都有哪几种工作队列
ArrayBlockingQueue
: 是一个基于数组结构的有界阻塞队列,此队列按FIFO
(先进先出)原则对元素进行排序。LinkedBlockingQueue
: 一个基于链表结构的阻塞队列,此队列按FIFO
(先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue
。静态工厂方法Executors.newFixedThreadPool()
使用了这个队列SynchronousQueue
: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
,静态工厂方法Executors.newCachedThreadPool
使用了这个队列。PriorityBlockingQueue
: 一个具有优先级的无限阻塞队列。
更多推荐
Java——常见的创建编程的手段(Thread、Callable、Executors)
发布评论