一、Callable接口
二、reentrantLock
三、原子类
四、线程池
五、信号量 Semaphore
六、CountDownLatch
JUC即java.utill.concurrent,里面放了一些多线程编程时有用的类,下面是里面的一些类。
一、Callable接口
1、创建线程的操作
多线程编程时,创建线程有以下五种操作:
1、继承Thread类(包含了匿名内部类的方式)
2、实现Runnable接口(包含了匿名内部类的方式)
3、基于lambda表达式
4、基于Callable接口
5、基于线程池
为什么有那么多方式可以创建线程,前面三个创建线程很方便,也经常用,为啥还要学Callable接口创建线程的方式呢?答案是因为有它独特的优势和特性。
以下是Callable和Runnable的区别:
Runnable关注的是这个的过程,也就是重新run方法里面的内容,它的返回值是void。
Callable即关注过程,也关注结果,Callable提供call方法,返回值就是执行任务得到的结果。
2、编写多线程代码
创建一个线程,这个线程完成1+2+3+...+1000的任务,并打印出结果。
(1)实现Runnable接口(使用匿名内部类)
代码如下:
public class ThreadDemo1 {
private static int sum = 0;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
int result = 0;
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
result += i;
}
sum = result;
}
});
t.start();
t.join();
System.out.println("sum = " + sum);
}
}
执行结果:
可以看到,用实现Runnable接口的代码可以完成任务,但并不优雅,因为要创建一个全局的静态变量,如果其他场景下使用Runnable接口,需要创建很多这样的变量,就容易混淆、记错这些变量的代指。
(2)实现Callable接口(使用匿名内部类)
代码如下:
public class ThreadDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println("result = " + result);
}
}
执行结果如下:
和预期值一样。
实现Callable接口,没有创建变量也可以完成任务,通过call方法的返回值,输出我们想要的值。
注意:
1、这里的FutureTask,因为Thread里面的构造方法的参数没有Callable接口,但有FutureTask类,所以成为了Thread和Callable的粘合剂。
2、FutureTask直接翻译的意思是:未来的任务,那么未来的任务肯定没有执行完,最终取结果的时候就需要一个凭据,而futureTask就是凭据;就像我们吃麻辣烫的时候,付完款拿到的小牌子,当前工作人员没做完麻辣烫,需要做完后叫到我们的号才能取餐。
3、FutureTask的get方法有阻塞功能,如果线程没有执行完,get就会阻塞,等线程执行完了,return了结果,才会执行get方法返回值。
Callable是一个“锦上添花”的东西,Callable能干的事,Runnable也能干,但对于这种带返回值的任务,使用Callable会更好,代码更直观、简单,但需要理解这里FutureTask起到的作用。