方式一:
1.先定义Callable对象,用于编写线程要执行代码(用call方法表示,此方法有返回值)
2.定义FutureTask对象封装Callable对象
3.定义Thread对象封装FutureTask对象, 并调用Thread对象的start()方法
具体代码如下:
package demo;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable t1=new MyCallable();FutureTask task1=new FutureTask(t1);Thread thread1=new Thread(task1,"T1");thread1.start();MyCallable t2=new MyCallable();FutureTask task2=new FutureTask(t2);Thread thread2=new Thread(task2,"T2");thread2.start();System.out.println(task1.get());System.out.println(task2.get());//注意:thread1和thread2最好分别执行两个task对象(两个task对象也分别采用两个Callable对象,否则只会有一个线程执行)}
}class MyCallable implements Callable<Integer>{public Integer call() throws Exception {int i=0;for(;i<10000;i++) {System.out.println(Thread.currentThread().getName()+" "+i);}return i;}
}
方式二:
2.1.newFixedThreadPool线程池的概述
newFixedThreadPool是Java中的一个线程池类,它是一个固定大小的线程池,线程的数量在创建线程池时就已经确定。线程池中的线程数量一旦被确定,就不会发生改变。在Java中,newFixedThreadPool()方法创建的线程池是一个固定大小的线程池,线程池中的线程数量是固定的,由构造函数传入的参数指定,而任务队列的大小则由内部的阻塞队列来决定。
在使用newFixedThreadPool()方法创建线程池时,它使用的是LinkedBlockingQueue,这是一个无界的阻塞队列,它的大小是没有限制的。因此,当任务提交到线程池时,如果线程池中的线程正在执行任务,那么新提交的任务将被放入LinkedBlockingQueue中等待执行,直到有可用的线程来执行任务。当队列已经满了时,新提交的任务将会被阻塞,直到有空闲线程来处理队列中的任务。
因此,使用newFixedThreadPool()方法创建线程池时,队列的大小实际上是无限制的,但是需要注意的是,如果任务提交速度过快,队列可能会无限制地增长,导致内存溢出等问题。因此,在实际使用中需要根据具体的场景来合理设置线程池的大小和任务队列的容量,以充分利用系统资源并保证系统的稳定性。
2.2.newFixedThreadPool线程池的优缺点:
A.优点
- 简单易用:newFixedThreadPool是Java内置的线程池,使用起来非常简单,不需要手动创建线程、管理线程、调度线程等。
- 稳定性好:newFixedThreadPool是一个固定大小的线程池,线程数量不会发生变化,因此稳定性较好,不容易因线程数量过多导致系统崩溃。
- 高效性:newFixedThreadPool在任务队列中维护了一个阻塞队列,可以很好地处理大量的并发请求,提高了程序的效率和性能。
- 良好的可扩展性:newFixedThreadPool可以通过修改线程池中的线程数量来适应不同的系统负载,提高了系统的可扩展性。
B.newFixedThreadPool的缺点
- 线程数固定:newFixedThreadPool创建时需要指定线程数量,线程数量不能动态调整,因此无法适应不同的系统负载。
- 队列长度有限:newFixedThreadPool使用阻塞队列存储任务,队列长度有限,如果队列已满,新的任务将无法提交,可能会导致任务丢失。
- 任务执行时间不可控:newFixedThreadPool无法控制任务执行的时间,如果任务执行时间过长,会导致其他任务等待的时间过长,降低程序的效率和性能。
2.3.线程池的两种主要任务提交方法:
在Java中,线程池的两种主要任务提交方法是execute()和submit()。它们虽然都是将任务提交到线程池中,但是在使用上有一些区别。
- 返回值类型不同
execute()方法没有返回值,而submit()方法会返回一个Future对象,可以通过这个对象获取任务的执行结果。
- 异常处理不同
execute()方法中如果任务执行过程中发生了异常,则异常会被传递到任务提交的地方,并由任务提交的线程来处理。而submit()方法中,如果任务执行过程中发生了异常,异常将被封装在Future对象中,直到调用Future.get()方法时才会将异常抛出。
- 任务参数不同
execute()方法只能接受Runnable类型的任务,而submit()方法既可以接受Runnable类型的任务,也可以接受Callable类型的任务。
- 提交方式不同
execute()方法是一种异步提交方式,即提交任务后立即返回,不会等待任务执行完成。而submit()方法是一种同步提交方式,即提交任务后会阻塞当前线程,直到任务执行完成。
- 能否取消任务
submit()方法返回的Future对象可以用来取消任务,而execute()方法没有提供取消任务的方法。
总之,execute()方法比submit()方法更简单,适用于不需要处理返回值的情况,而submit()方法则更为灵活,可以处理返回值,并且支持取消任务等操作。
用Callable创建的线程在线程池中运行,不需要FutureTask对象也不需要Thread对象
可以两个线程可以采用同一个Callable对像,也可以一个线程采用一个Callable对像
package demo;import java.util.concurrent.*;//用Callable创建的线程在线程池中运行
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable c1=new MyCallable();//MyCallable c2=new MyCallable();ExecutorService service= Executors.newFixedThreadPool(2);//线程池的方式可以两个线程可以采用同一个Callable对像,也可以一个线程采用一个Callable对像Future<Integer> f1=service.submit(c1);//不需要FutureTask对象也不需要Thread对象Future<Integer> f2=service.submit(c1);Integer rs1=f1.get();System.out.println(rs1);Integer rs2=f2.get();System.out.println(rs2);//service.shutdown();//关闭线程池}
}class MyCallable implements Callable<Integer>{public Integer call() throws Exception {int i=0;for(;i<100;i++) {System.out.println(Thread.currentThread().getName()+" "+i);}return i;}
}
用Runnable方式创建线程要执行的任务提交给newFixedThreadPool线程池来执行,代码如下:
package demo;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;//使用newFixedThreadPool线程池
public class Test {public static void main(String[] args) {ExecutorService service= Executors.newFixedThreadPool(3);//创建service.execute(new Task());//提交要执行的任务(此任务会由线程池的某一个线程来运行)service.execute(new Task());//提交要执行的任务(此任务会由线程池的另一个线程来运行)//service.shutdown();//关闭线程池}
}class Task implements Runnable{public void run() {for(int i=1;i<=5;i++){System.out.println(Thread.currentThread().getName()+":run"+i);}}
}