今日内容
- Lock
- 并发包
- 线程池
教学目标
- 能够使用Lock解决线程安全问题
- 能够描述ConcurrentHashMap类的作用
- 能够描述CountDownLatch类的作用
- 能够描述CyclicBarrier类的作用
- 能够表述Semaphore类的作用
- 能够描述Exchanger类的作用
- 能够使用线程池
第一章 Lock锁(掌握)
从jdk5后java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大.Lock锁是Java中更加符合编程习惯的解决方式。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
由于Lock属于接口,不能创建对象,所以我们可以使用它的子类ReentrantLock来创建对象并使用Lock接口中的函数。
需求:使用Lock实现线程安全的卖票。
分析和步骤:
1)定义一个卖票的任务类SellTicketTask 类并实现Runnable接口;
2)在任务类中定义一个成员变量tickets保存票数100;
3)定义一把锁Lock的对象l;
4)在run函数中模拟卖票,if语句的上面使用锁对象l调用lock()函数获取锁,等待if语句结束之后,使用锁对象l调用unlock()函数释放锁;
5)定义测试类SellTicketDemo ,在这个类中分别创建任务类的对象和线程类的对象,并使用线程类的对象调用start()函数来启动线程;
/*
* 需求:使用Lock实现线程安全的卖票。
* Lock是接口,只能通过他的子类ReentrantLock创建对象
* 构造函数 ReentrantLock() 创建一个 ReentrantLock 的实例。
* void lock() 获取锁。
* void unlock() 试图释放此锁。
*/
//定义一个任务类用来卖票
class SellTicketTask implements Runnable
{
//定义100张票
private static int tickets=100;
//创建对象作为任意一把锁
// private Object obj=new Object();
//定义一把锁
Lock l=new ReentrantLock();
//模拟卖票
public void run() {
/*while(true)
{
synchronized (obj) {
if(tickets>0)
{
System.out.println(Thread.currentThread().getName()+"出票:"+tickets--);
}
}
}*/
//使用Lock锁替换synchronized
while(true)
{
//获取锁
l.lock();
if(tickets>0)
{
try {Thread.sleep(1);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"出票:"+tickets--);
}
//释放锁
l.unlock();
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 创建任务类对象
SellTicketTask stt = new SellTicketTask();
//创建线程对象
Thread t1 = new Thread(stt,"窗口1");
Thread t2 = new Thread(stt,"窗口2");
Thread t3 = new Thread(stt,"窗口3");
Thread t4 = new Thread(stt,"窗口4");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
第二章 并发包
在JDK的并发包里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。
2.1 CopyOnWriteArrayList(理解)
-
ArrayList和CopyOnWriteArrayList效果演示
-
ArrayList在多线程的情况下线程不安全
-
CopyOnWriteArrayList是一个线程安全的集合,解决ArrayList安全问题。 数据不唯一。
- 这个类的内部其实也就是使用了同步锁解决线程安全问题,和我们自己写同步锁是一样的意思。
-
-
需求:给list集合添加1000个元素,然后打印集合的长度。
public class MyRun implements Runnable{ //定义一个集合 //普通的ArrayList是线程不安全的类 //static ArrayList<Integer> list = new ArrayList<>(); //只要换成CopyOnWriteArrayList就变成了线程安全的 static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); @Override public void run() { //给集合添加了1000个元素 for (int i = 0; i < 1000; i++) { list.add(i); } } } public class Demo_ArrayList { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //开启线程 Thread t1 = new Thread(mr); t1.start(); Thread t2 = new Thread(mr); t2.start(); //为了让for循环先执行结束再打印 //睡两秒钟 Thread.sleep(2000); //在这里打印集合的长度,里面应该存放了多少个元素? System.out.println(MyRun.list.size()); } } 执行效果: ArrayList: 一个线程给集合添加1000个元素,两个线程应该添加2000个元素 但是打印的结果是小于2000或者可能会抛异常 因为ArrayList集合本身就是线程不安全的 CopyOnWriteArrayList: 结果是2000
2.2 CopyOnWriteArraySet(理解)
- HashSet是线程不安全的
- CopyOnWriteArraySet是线程安全的
- 需求:向HashSet中存储1000个数字。
代码演示如下:
package com.itheima.sh.demo_14;
public class MyRun implements Runnable {
//定义集合
//HashSet集合本身是有线程安全问题的
// static HashSet<Integer> set = new HashSet<>();
//CopyOnWriteArraySet解决了安全问题
static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
//添加1000个数字 0-999
for (int i = 0; i < 1000; i++) {
set.add(i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyRun mr = new MyRun();
//开启线程
Thread t1 = new Thread(mr);
t1.start();
//开启线程
Thread t2 = new Thread(mr);
t2.start();
//让主程序睡两秒钟
Thread.sleep(2000);
//打印集合的长度 大于1000
System.out.println(MyRun.set.size());
}
}
执行效果:
HashSet:
本来集合中保存的是1000个元素,但是打印的长度size大于1000.
CopyOnWriteArraySet:
打印的集合长度就是1000
说明:上述打印集合的长度size大于1000的原因如下图所示:
假设线程一和线程二都向set集合中添加数据0,线程一正在添加的过程中先判断集合中是否含有0,如果没有则添加数字0,此时线程二也开始判断set集合中是否有0,此时没有则线程二也准备添加,添加一次,那么底层size就会+1,但实际上set集合只存储一个数字0.size加错了。
注意:
CopyOnWriteArraySet 底层就是CopyOnWriteArrayList 。CopyOnWriteArraySet 如何保证唯一的?使用CopyOnWriteArrayList中的方法:
boolean addIfAbsent(E e) 添加元素(如果不存在)。
如果添加的元素存在,则不存储,如果添加元素不存在则添加。
举例:
aaa 第一次添加可以添加
bbb 第一次添加可以添加
aaa 已经存在aaa 不添加
2.3 ConcurrentHashMap(理解)
-
HashMap和Hashtable和ConcurrentHashMap效果演示
-
需求:向HashMap集合中存储1000个键值对数据
import java.util.HashMap; import java.util.Hashtable; import java.util.concurrent.ConcurrentHashMap; public class MyRun implements Runnable { //HashMap线程不安全 //HashMap<Integer,Integer> map = new HashMap<>(); //Hashtable线程安全的 //Hashtable<Integer,Integer> map = new Hashtable<>(); //ConcurrentHashMap线程安全的 ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>(); @Override public void run() { //添加1000个元素 0-999 for (int i = 0; i < 1000; i++) { map.put(i,i); } } } public class DemoHashMap { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //开启线程 Thread t1 = new Thread(mr); t1.start(); //开启线程 Thread t2 = new Thread(mr); t2.start(); //先睡2秒钟让循环执行结束 Thread.sleep(2000); //打印集合的长度 System.out.println(mr.map.size()); } } 执行效果: HashMap本来应该打印的长度是1000,但是执行的结果大于1000 Hashtable 结果是1000 ConcurrentHashMap 结果是1000
Hashtable和ConcurrentHashMap的速度区别
-
Hashtable解决线程安全的方式,但是效率很低。
public synchronized V get(Object key) {} public synchronized V put(K key, V value) {} 每个方法全都是同步的,当任意一个方法在执行时,别的方法都不能执行。
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
-
ConcurrentHashMap
CAS + 局部(synchronized)锁定
-
-
Hashtable和ConcurrentHashMap效率高低的代码演示
public class MyRun implements Runnable { //HashMap线程不安全 //HashMap<Integer,Integer> map = new HashMap<>(); //Hashtable线程安全的 //Hashtable<Integer,Integer> map = new Hashtable<>(); //ConcurrentHashMap线程安全的 ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>(); @Override public void run() { //获取系统当前时间 long time1 = System.currentTimeMillis(); //添加1000个元素 0-1999 for (int i = 0; i < 2000; i++) { map.put(i,i); } long time2 = System.currentTimeMillis(); System.out.println((time2-time1) + "毫秒"); } } public class DemoHashMap { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //开启线程 //开启了很多个线程能够看到更明显的速度的区别 for (int i = 0; i < 1000; i++) { Thread t1 = new Thread(mr); t1.start(); } } }
2.4 CountDownLatch(了解)
-
作用
CountDownLatch允许一个或多个线程等待其他线程完成操作。
-
方法介绍
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象 void await() // 让当前线程等待 void countDown() //每调用一次countDown()方法,计数器count就会进行减1,如果减到0等待的线程就会执行
-
代码演示
- 要求: 必须在先打印C 再打印B
import java.util.concurrent.CountDownLatch; public class MyRun1 implements Runnable { CountDownLatch latch; //使用构造方法传入参数 public MyRun1(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("A"); /*try { Thread.sleep(1000);//效率太低 } catch (InterruptedException e) { e.printStackTrace(); }*/ //等待 try { latch.await(); } catch (InterruptedException e) { } System.out.println("B"); } } import java.util.concurrent.CountDownLatch; public class MyRun2 implements Runnable{ CountDownLatch latch; //使用构造方法传入参数 public MyRun2(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("C"); //计数器减一 //latch在计数器等于0的时候就会让等待的线程执行 latch.countDown(); } } import java.util.concurrent.CountDownLatch; //有一个要求:必须在先打印C 再打印B public class Test01 { public static void main(String[] args) throws InterruptedException { //创建对象 //这里的参数1表示计数器,只要执行latch.countDown();那么计数器就会减1 CountDownLatch latch = new CountDownLatch(1); //开启线程 MyRun1 myRun1 = new MyRun1(latch); Thread t = new Thread(myRun1); t.start(); //开启线程 MyRun2 myRun2 = new MyRun2(latch); Thread t2 = new Thread(myRun2); t2.start(); } }
如果没有使用CountDownLatch的情况下,会出现如下所示结果:
小结:
CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。
CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch的await()方法的线程阻塞状态解除,继续执行。
2.5 CyclicBarrier(了解)
概述
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
-
作用
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
-
方法介绍
public CyclicBarrier(int parties, Runnable barrierAction) // 创建对象 //parties代表要达到屏障的线程数量 //barrierAction达到屏障之后要执行的线程 int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
-
示例代码
需求:有5个学生一起考试,考试有100道题,每个人答题速度不一样,但是要求,所有人做完之后才能交卷。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class MyRun implements Runnable { //创建屏障对象 CyclicBarrier cb = new CyclicBarrier(5,new JiaoJuan()); @Override public void run() { //考试一共100道题 for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + "做完了第" + i + "道题"); } System.out.println(Thread.currentThread().getName() + "做完了所有题!"); //等待别人写题 try { cb.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } } public class JiaoJuan implements Runnable { @Override public void run() { System.out.println("大家一起交卷!!!!"); } } public class Demo01 { public static void main(String[] args) { //创建线程 MyRun mr = new MyRun(); //开启5个线程 new Thread(mr,"张三").start(); new Thread(mr,"李四").start(); new Thread(mr,"王五").start(); new Thread(mr,"赵六").start(); new Thread(mr,"田七").start(); } }
使用场景
使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
例如:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。
2.6 Semaphore(了解)
-
作用
1)Semaphore的主要作用是控制线程的并发数量。
2)之前学习的synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。Semaphore 可以设置同时允许几个线程执行。
3)Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
-
方法介绍
public Semaphore(int permits) //permits 表示许可线程的数量 void acquire() //表示获取许可 void release() //表示释放许可
-
代码演示
需求:开了一个饭店,饭店里面只有3张桌子,有5桌人来吃饭。
//饭店 public class FanDian { //定义Semaphore控制最大接客数量 //public Semaphore(int permits)permits 表示许可线程的数量 Semaphore s = new Semaphore(3); //饭店的服务吃饭方法(方法名无所谓) public void service() throws InterruptedException { //获取方法 //public void acquire() throws InterruptedException 表示获取许可 s.acquire(); //调用了这个方法就表示进来了 //显示进来的时间 Calendar c = Calendar.getInstance(); int minute = c.get(Calendar.MINUTE); int second = c.get(Calendar.SECOND); System.out.println(Thread.currentThread().getName() + minute + "分钟" + second + "秒进来吃饭"); //吃饭 //等待3秒钟 模拟吃饭的过程,表示线程正在吃饭 Thread.sleep(3000); //显示出去的时间 Calendar c2 = Calendar.getInstance(); int minute2 = c2.get(Calendar.MINUTE); int second2 = c2.get(Calendar.SECOND); System.out.println(Thread.currentThread().getName() + minute2 + "分钟" + second2 + "秒吃完饭出去了"); //释放 //public void release() 表示释放许可 s.release(); } } public class MyRun implements Runnable { //创建对象 FanDian fd = new FanDian(); @Override public void run() { //调用吃饭的方法 try { fd.service(); } catch (InterruptedException e) { } } } public class Test01 { public static void main(String[] args) throws InterruptedException { //创建线程 MyRun mr = new MyRun(); //开启5个线程 new Thread(mr,"张三").start(); new Thread(mr,"李四").start(); new Thread(mr,"王五").start(); new Thread(mr,"锁哥").start(); new Thread(mr,"赵六").start(); } }
2.7 Exchanger(了解)
概述
-
作用
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行2个线程间的数据交换。
说明:
两个线程通过exchange()方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
-
方法介绍
public Exchanger() //构造方法,创建对象 public V exchange(V x) //参数是给对方的数据,返回值是对方发来的数据 //说明:如果一个线程执行了exchange(),此时就会阻塞等待另一个线程执行exchange()方法,才结束阻塞
-
代码演示
需求:在一个银行,要统计今年收入一共是多少,让两个人都去计算,算完之后两个人互相交换数据。
import java.util.concurrent.Exchanger; public class MyRun1 implements Runnable { Exchanger<String> e; //构造方法 public MyRun1(Exchanger<String> e) { this.e = e; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始计算今年的收入。。"); String s = null; try { s = e.exchange("收入表A"); } catch (InterruptedException e1) { } System.out.println(Thread.currentThread().getName() + "收到了 " + s); } } public class MyRun2 implements Runnable { Exchanger<String> e; //构造方法 public MyRun2(Exchanger<String> e) { this.e = e; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始计算今年的收入。。。"); String s = null; try { s = e.exchange("收入表B"); } catch (InterruptedException e1) { } System.out.println(Thread.currentThread().getName() + "收到了 " +s); } } import java.util.concurrent.Exchanger; public class Demo { public static void main(String[] args) { //在主方法中创建对象 //泛型用来代表交换的数据的类型 Exchanger<String> e = new Exchanger<>(); MyRun1 myRun1 = new MyRun1(e); new Thread(myRun1,"张三").start(); MyRun2 myRun2 = new MyRun2(e); new Thread(myRun2,"李四").start(); } }
使用场景
使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,
并对两个文件数据进行校对,看看是否录入一致,
【自学,了解,有兴趣的同学可以自学下】
- exchange方法的超时
1).制作线程A:
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...");
System.out.println("在线程A中得到线程B的值 =" + exchanger.exchange("礼物A",5, TimeUnit.SECONDS));
System.out.println("线程A结束!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("5秒钟没等到线程B的值,线程A结束!");
}
}
}
2).制作测试类:
public class Run {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}
3).测试结果:
第三章 线程池方式
3.1 线程池的思想
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来讲解一下Java的线程池。
3.2 线程池概念
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。因为启动线程的时候会在内存中开辟一块空间,消耗系统资源,同时销毁线程的时候首先要把和线程相关东西进行销毁,还要把系统的资源还给系统。这些操作都会降低操作性能。尤其针对一个线程用完就销毁的更加降低效率。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,生存期较短的线程指的是用完一次线程就丢掉。更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
线程池工作原理如下图所示:
需求:我有一段任务,需要执行100次。
说明:
每次线程执行完任务以后,线程不会销毁,会放回线程池中,每次在执行任务的时候又会到线程池中去取线程。这样会提高效率。
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机或者宕机)。
3.3 线程池的使用
Java里面线程池的顶级接口是java.util.concurrent.Executor
,以及他的子接口java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
-
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
A:自定义一个类,作为任务类并实现Runnable接口;
B:实现Runnable接口中的run方法;
C:创建任务类的对象;
D:获取线程池对象;
E:直接执行任务;
需求:使用线程池来完成卖票任务。
Runnable实现类代码:
//A:自定义一个类,作为任务类并实现Runnable接口;
class SellTicketTask implements Runnable
{
//定义成员变量存储100张票
private static int tickets=100;
//创建锁对象
private Lock l=new ReentrantLock();
//B:实现Runnable接口中的run方法;
public void run() {
// 模拟卖票
while(true)
{
//获取锁
l.lock();
if(tickets>0)
{
//休眠
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"出票:"+tickets);
tickets--;
}
//释放锁
l.unlock();
}
}
}
线程池测试类:
public class SellTicketDemo {
public static void main(String[] args) {
//C:创建任务类的对象;
SellTicketTask stt = new SellTicketTask();
//D:获取线程池对象; 获取2个线程
ExecutorService es = Executors.newFixedThreadPool(2);
//E:直接执行任务;
//自己创建线程对象的方式
// Thread t = new Thread(stt);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用SellTicketTask中的run()
es.submit(stt);
// 再获取个线程对象,调用SellTicketTask中的run()
es.submit(stt);
}
}
3.4 Callable开启多线程
-
<T> Future<T> submit(Callable<T> task)
: 获取线程池中的某一个线程对象,并执行.问题1:Callable是什么?
/*
* 演示:演示Callable
* 我们忽略返回值,这个接口就与Runnable接口一样了
*/
class MyTask implements Callable<Object>{
@Override
public Object call() throws Exception {
for( int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " ... " + i);
}
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
// 创建任务对象
MyTask mt = new MyTask();
// 获取线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 执行任务
es.submit(mt);
es.submit(mt);
}
}
问题2:Future是什么?
- 方法:
V get()
: 获取计算完成的结果。 - 需求:通过Callable计算从1到5的和
A:我们自定义类,实现Callable接口
B:实现Call方法,Call方法有返回值
C:然后吧任务类对象交给线程池执行
D:执行完成的结果保存Future中
E:最后我们调用Future的get方法拿到真正的结果。
/*
* 演示:带返回值的线程任务
* 需求:通过Callable计算从1到任意数字的和
*/
class SumTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= 5; i++){
sum += i;
}
return sum ;
}
}
public class CallableDemo02 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建任务对象
SumTask st = new SumTask();
// 获取线程池
ExecutorService es = Executors.newFixedThreadPool(1);
// 执行任务
Future<Integer> future = es.submit(st);
// 等待运算结束,获取结果
Integer i = future.get();
System.out.println(i);
}
}