ExecutorService- Future - Java多线程编程

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在将来某个时刻获取结果

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值