在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动、调度、管理线程的一大堆API了。在Java5以后,通过 Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。
一:创建任务
任务就是一个实现了Runnable接口的类。
创建的时候实现run方法即可。
二:执行任务
通过java.util.concurrent.ExecutorService接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和Callable 类的工厂和实用方法。
ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
executorService.execute(newTestRunnable());
1、创建ExecutorService
通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
比如,创建一个ExecutorService的实例,ExecutorService实际上是一个线程池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
2、将任务添加到线程去执行
当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。
示例1:
@Test
public void testSingleThreadExecutor() throws Exception {
// 单例线程,任意时间(同一时间)池中只能有一个线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
System.err.println("线程启动并运行: " + Thread.currentThread().getName());
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("第二个线程启动了: " + Thread.currentThread().getName());
}
});
}
运行结果如下:
可以看到示例1中,两个线程都会执行,但是只会使用一个线程来运行。
示例2:
@Test
public void testCachedThreadPool() throws Exception {
// 声明一个线程池
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 4; i++) {
final int a = i;
// 每一次调用execute方法,都会向线程池中放入一个对象
es.execute(new Runnable() {
@Override
public void run() {
while (true) {
System.err.println("测试...."+ a +">"+
Thread.currentThread().getName()+","+
Thread.currentThread().isDaemon());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
es.shutdown();
es.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
}
从运行结果可以看出,每四个一组输出,即一共创建了四个线程,每次每个线程都会执行输出,但不按顺序,每一次输出都四个算是一组。
这里有两个方法:
shutdown():当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态,以后不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS) : 接收timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
示例3:
@Test
public void testCall() throws Exception {
ExecutorService es = Executors.newCachedThreadPool();//创建线程池对象
List> result = new ArrayList>();//放结果用的集合
for (int i = 0; i < 3; i++) {
Future f= es.submit(new MyCall(i));//线程执行完成以后可以通过引用获取返回值
result.add(f);
}
for(Future f:result){
System.err.println("返回值:"+f.get());//输出返回的值
}
System.err.println("完成....");
}
class MyCall implements Callable {
private int seq;
public MyCall(int seq){
this.seq=seq;
}
@Override
public String call() throws Exception {
System.err.println("执行"+seq+","+Thread.currentThread().getName());
Thread.sleep(3000);
System.err.println("Weak up "+seq);
return "完成"+seq;// 这是返回值
}
}
可以看出在所有线程运行结束之后,返回结果。
总结:
execute和submit 区别:
他们接收的参数不同。
execute没有返回值,而submit有返回值。
当然我之前写过了单线程断点下载和多线程断点下载以及多线程下载
下面就现学现用,在多线程多点下载中我简单的使用submit,当然我这里只是简单的演示,和真正的企业级还是有很大的差距。
直接上代码:
package com.demo.servlet.filedownload;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 多线程下载同一个文件
*/
public class MultiThreadDown4 {
//声明线程池大小
private static int THREAD_POOL_SIZE= 3;
public MultiThreadDown4() throws Exception {
String fileDownName = "jetty.zip";
//声明url
String path = "http://localhost:8080/tianpengfei/up/" + fileDownName;
//第一步:声明url对象
URL url = new URL(path);
//第二步:返回连接对象
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//第三步:设置请求类型
con.setRequestMethod("GET");
//第四步接收信息
con.setDoInput(true);
//第五步:连接
con.connect();
//6:状态码
int code = con.getResponseCode();
if(code==200){
long srcFileSize = con.getContentLength();
//7.1有了文件的长度,直接创建一个相同大小的文件
String fileName = "d:/a/" + fileDownName;
RandomAccessFile file = new RandomAccessFile(new File(fileName),"rw");
long localFileSise = file.length();
file.close();
//8:声明线程的个数
// long threadCount = 3;
//9:计算每个线程的下载量
long threadSize = 0l;
System.out.println("================> 还需要下载的总量为:" + (srcFileSize-localFileSise));
if (localFileSise == 0l) {
threadSize = srcFileSize/THREAD_POOL_SIZE +(srcFileSize%THREAD_POOL_SIZE==0?0:1);
} else {
threadSize = (srcFileSize-localFileSise)/THREAD_POOL_SIZE +((srcFileSize-localFileSise)%THREAD_POOL_SIZE==0?0:1);
}
System.err.println("每个线程下载的数据量:"+threadSize);
//10:计算每个线程下载的数据量
long start = 0l;
long end = 0l;
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
List> result = new ArrayList>();
for(long i=0;i f = executor.submit(new MyCallable(url, fileName, start, end));
result.add(f);
}
long resultSum = 0;
for (Future f : result) {
System.err.println("返回值:" + f.get());// 输出返回的值
resultSum += f.get();
}
System.err.println("完成....");
System.out.println("resultSum: "+resultSum);
if (resultSum == 3) {
System.out.println("真的下载完了");
}
}
con.disconnect();
}
public static void main(String[] args) throws Exception {
new MultiThreadDown4();
}
}
class MyCallable implements Callable{
private URL url;
private String fileName;
private long start;
private long end;
public MyCallable(URL url, String fileName, long start, long end) {
this.url = url;
this.fileName = fileName;
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
try{
//打开连接
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//设置
con.setRequestMethod("GET");
con.setDoInput(true);
//设置从哪儿开始下载数据
con.setRequestProperty("range","bytes="+start+"-"+end);
con.connect();
int code = con.getResponseCode();
if(code==206){
int size = con.getContentLength();
System.err.println("线程:"+Thread.currentThread().getName()+",下载的数据量为:"+size);
InputStream in = con.getInputStream();
//写同一文件
RandomAccessFile file = new RandomAccessFile(new File(fileName),"rw");
//设置从文件的什么位置开始写数据
file.seek(start);
//读取数据
byte[] b = new byte[4];
int len = 0;
while((len=in.read(b))!=-1){
file.write(b,0,len);
}
file.close();
}
con.disconnect();
}catch(Exception e){
e.printStackTrace();
}
return 1;
}
}