JAVA语言内置了多线程的支持:
1. 但是创建线程需要操作系统的资源(例如线程本身的资源,栈的空间等等)
2. 如果我们频繁的创建和销毁线程,就需要消耗大量的时间.
如果我们可以复用一个线程,假如我们有大量的小任务,我们就可以让它们排队执行,然后在一个线程池里,
由少量的线程执行大量的任务.
线程池:
1. 所以线程池可以维护若干个线程,让他们处于等待状态.
2. 如果有新任务,就分配一个空闲的线程来执行这个任务.
3. 如果所有的线程都处于忙碌状态,新任务就放入队列等待.
ExecutorService来表示一个线程池,我们通常使用ExecutorService的静态方法,
比如newFixedThreadPool来创建一个固定大小的线程池,然后我们通过一个submi()方法提交一个任务
JDK提供的常用的ExecutorService包括:
1. FixedThreadPool: 线程数固定的线程池
2. CachedThreadPool: 线程数根据任务动态调整的线程池
3. SingleThreadExecutor: 这个线程池只包含一个线程,也就是所有的任务只能以单线程的形式执行
package com.leon.day05;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// PrintTask继承自Runnable
class PrintTask implements Runnable {
String name;
public PrintTask(String name) {
this.name = name;
}
@Override
public void run() {
// run方法中我们执行多次,大概需要3秒钟
for(int i=0;i<3;i++) {
System.out.println("Hello, " + name + "!");
try {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
}
}
}
}
public class ThreadPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new PrintTask("Bob"));
executor.submit(new PrintTask("Alice"));
executor.submit(new PrintTask("Tim"));
executor.submit(new PrintTask("Robot"));
Thread.sleep(100);
executor.shutdown();
}
}
package com.leon.day05;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// PrintTask继承自Runnable
class PrintTask implements Runnable {
String name;
public PrintTask(String name) {
this.name = name;
}
@Override
public void run() {
// run方法中我们执行多次,大概需要3秒钟
for(int i=0;i<3;i++) {
System.out.println("Hello, " + name + "!");
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
}
}
}
}
public class ThreadPool {
public static void main(String[] args) throws InterruptedException {
// 我们改成SingleThreadPool
// 这个时候我们发现所有的任务都是以串行的方式执行的,因为我们的线程池里面只有一个线程
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new PrintTask("Bob"));
executor.submit(new PrintTask("Alice"));
executor.submit(new PrintTask("Tim"));
executor.submit(new PrintTask("Robot"));
Thread.sleep(100);
executor.shutdown();
}
}
package com.leon.day05;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// PrintTask继承自Runnable
class PrintTask implements Runnable {
String name;
public PrintTask(String name) {
this.name = name;
}
@Override
public void run() {
// run方法中我们执行多次,大概需要3秒钟
for(int i=0;i<3;i++) {
System.out.println("Hello, " + name + "!");
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
}
}
}
}
public class ThreadPool {
public static void main(String[] args) throws InterruptedException {
// 我们再改成CachedThreadPool,由于CachedThreadPool会根据我们的任务动态的调整线程的数量
// 所以这四个任务提交进去以后,线程池会立刻创建四个线程,
// 如果我们想要限制线程池的上限,最多10个线程,
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new PrintTask("Bob"));
executor.submit(new PrintTask("Alice"));
executor.submit(new PrintTask("Tim"));
executor.submit(new PrintTask("Robot"));
Thread.sleep(100);
executor.shutdown();
}
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
cachedThreadPool的源码,实际上就是一个ThreadPoolExecutor的实例,我们可以复制一下代码.
第一个参数是corePoolSize,第二个参数maximunPoolSize线程池最大的大小,我们可以把第二个参数调整为10
package com.leon.day05;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// PrintTask继承自Runnable
class PrintTask implements Runnable {
String name;
public PrintTask(String name) {
this.name = name;
}
@Override
public void run() {
// run方法中我们执行多次,大概需要3秒钟
for(int i=0;i<3;i++) {
System.out.println("Hello, " + name + "!");
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
}
}
}
}
public class ThreadPool {
public static void main(String[] args) throws InterruptedException {
// 这里就是10个线程的线程池
ExecutorService executor = new ThreadPoolExecutor(0, 10,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
executor.submit(new PrintTask("Bob"));
executor.submit(new PrintTask("Alice"));
executor.submit(new PrintTask("Tim"));
executor.submit(new PrintTask("Robot"));
Thread.sleep(100);
executor.shutdown();
}
}
JDK还提供了一个ScheduledThreadPool
1. 它可以把一个任务定期的反复的执行
ScheduledThreadPool他又两种执行模式
一种是 Fixed Rate
一种是 Fixed Delay
Fixed Rate 是指在固定的间隔任务就会执行,例如每隔三秒钟任务就会启动,而不管任务到底执行了多长
时间.
Fixed Delay 是指任务执行完毕以后,我们等待一秒钟,再接着执行.无论任务执行多久,只有在任务执行结束
以后,等待一秒钟,才会执行下一次的任务.
package com.leon.day05;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 在HelloTask中打印两行语句,然后中间间隔1秒钟
* 所以执行这个任务大概需要一秒
* @author Leon.Sun
*
*/
class HelloTask implements Runnable {
String name;
public HelloTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Hello, " + name + "! It is " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Goodbye, " + name + "! It is " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}
public class Schedule {
public static void main(String[] args) {
// 先创建一个Schedule的ExecutorService,然后提交两个任务
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
// 第一个任务我们以FixedRate的模式执行,2表示两秒以后执行,参数5表示每隔5秒执行这个任务
executor.scheduleAtFixedRate(new HelloTask("Bob"), 2, 5, TimeUnit.SECONDS);
// 第2个任务我们以fixedDelay来运行,参数2表示我们会在2秒以后来执行,参数5表示间隔是5秒钟
executor.scheduleWithFixedDelay(new HelloTask("Alice"), 2, 5, TimeUnit.SECONDS);
}
}
由于Schedule的ThreadPool不会自动停止,所以我们强制结束虚拟机
我们观察执行的结果,我们可以看到Hello Bob的会执行的比较快,每隔5秒钟就会执行,
而Hello Alice这个任务,它执行的任务会比较低,因为它会间隔5秒钟才会执行
1. 我们使用ScheduledThreadPool会有一个问题,在FixedRate模式下,如果执行的任务时间过长,
后续的任务会不会导致并发执行呢?
2. 如果任务抛出了异常,后续任务是否还是继续执行呢?
大家可以通过代码简单的验证一下
JDK还提供了一个java.util.Timer的类
这个类也可以定期的执行一个任务
1. 一个Timer类只会对应一个Thread类,所以只会定期执行一个任务
2. 如果我们要执行多个定期任务,就必须启动多个Timer,而一个ScheduledThreadPool就可以调度多个任务
所以我们现在完全可以使用ScheduledThreadPool取代旧的Timer类
1. JDK提供的ExecutorService实现了线程池的功能
2. 线程池内部维护一组线程,可以高效执行大量的小任务
3. Executors提供了静态方法创建不同类型的ExecutorService
4. 必须调用shutdown()关闭ExecutorService
5. ScheduleThreadPool可以定期调度多个任务
但是这里我们提交一个Task,因为它继承自Runnable
因此JDK又提供了一个Callable接口
我们可以把一个Callable的Task交给Executor异步执行,当我们实现Callable接口的时候,我们需要
实现一个泛型接口,例如我们期望的返回值是一个String,我们就写Callable<String>,我们要复写call方法
我们如何获取一个异步执行的结果呢?
当我们提交一个Callable的任务以后,我们会获得一个Future<String>对象,然后我们在主线程的某个时刻
调用future对象的get()方法,如果异步结果完成,我们就直接获得结果,如果异步任务还没有完成,那么get方法
会阻塞,直到任务完成以后才会有结果.
Future接口表示未来可能会返回的结果,通过get方法返回一个异步返回的结果
cacel方法会终止一个异步任务的执行,isDone()方法可以判断当前的异步任务是否已经完成.
package com.leon.day05;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 有一个Callable接口
* @author Leon.Sun
*
*/
class DownloadTask implements Callable<String> {
String url;
public DownloadTask(String url) {
this.url = url;
}
// 我们复写call方法
public String call() throws Exception {
System.out.println("Start download " + url + "...");
URLConnection conn = new URL(this.url).openConnection();
conn.connect();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
String s = null;
StringBuilder sb = new StringBuilder();
while ((s = reader.readLine()) != null) {
sb.append(s).append("\n");
}
return sb.toString();
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
// 我们先创建一个Executor
ExecutorService executor = Executors.newFixedThreadPool(3);
// 我们可以下载一个指定URL的网页
// 我们创建一个DownloadTask,传入一个URL
DownloadTask task = new DownloadTask("http://www.baidu.com/");
// 最后我们把下载好的网页以String的方式返回
// 我们提交这个任务
Future<String> future = executor.submit(task);
// 然后通过future的get方法获得
String html = future.get();
System.out.println(html);
// 关闭这个线程池
executor.shutdown();
}
}
当我们不需要返回结果的时候,我们可以提交一个Runnable的任务,如果我们需要一个返回结果的任务的时候
我们就提交一个Callable的任务
1. 提交一个Callable的任务,可以获得一个Future对象
2. 可以用Future在将来某个时刻获取结果