目录
Callable
ReentrantLock
Semaphore
CountDownLatch
JUC即 java.util.concurrent,其中存放了一些进行多线程编程时有用的类
Callable是一个接口,在我们实现Runnable创建线程时,Runnable关注的是其过程,而不关注其执行结果(其返回值为void),而Callable会关注执行结果,Callable提供了call方法,其返回值就是线程执行任务得到的结果
因此,当我们在编写多线程代码时,希望得到线程种代码的返回值时,就可以使用Callable
例如:一个线程需要实现 1 + 2 + 3 + ... + 100,并返回其结果
若我们使用实现Runnable的方式创建线程时:
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int ret = 0;
for (int i = 1; i <= 100; i++) {
ret += i;
}
}
});
t.start();
t.join();
}
}
若此时主线程需要获取到其计算结果,则需要使用一个成员变量来保存结果
public class Demo1 {
private static int sum = 0;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int ret = 0;
for (int i = 1; i <= 100; i++) {
ret += i;
}
sum = ret;//保存结果
}
});
t.start();
t.join();
System.out.println(sum);
}
}
而若我们使用Callable时:
Callable其中泛型参数表示返回值的类型
Callable callable = new Callable() {
@Override
public Integer call() throws Exception {
int ret = 0;
for (int i = 1; i <= 100; i++) {
ret += i;
}
return ret;
}
};
此时就不必引入额外的成员变量了,直接使用返回值即可
然而,Thread未提供构造函数来传入callable
此时,我们需要使用 FutureTask 来作为Thread和callable的“粘合剂”
将callable实例使用FutureTask包装一下,再在构造方法传入FutureTask:
此时线程就会执行FutureTask内部的Callable中的call方法,计算完成后,就将结果放入FutureTask对象中
FutureTask泛型参数表示结果的类型
此时在主线程中调用FutureTask中的get()方法(带有阻塞功能),等待计算完毕,从FutureTask中获取到结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new Callable() {
@Override
public Integer call() throws Exception {
int ret = 0;
for (int i = 1; i <= 100; i++) {
ret += i;
}
return ret;
}
};
FutureTask futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
//此时无需使用join,使用futureTask中的get()方法获取到结果
System.out.println(futureTask.get());
}
}
观察两种实现方式,我们可以发现:
使用Callable和FutureTask,使代码简化了很多,且不用使用成员变量来保存结果
Callable和Runnable相对,都是描述一个“任务”,Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务
Callable通常需要搭配FutureTask使用,FutureTask用来保存Callable的返回结果
ReentrantLock是一种可重入互斥锁,与synchronized类似,都是用来实现互斥效果,保证线程安全的
ReentrantLock的用法:
lock():加锁,若获取不到锁就死等
unlock():解锁
tryLock(超时时间):加锁,若获取不到锁,等待时间一定后就放弃加锁
当我们在使用ReentrantLock时,需要加锁和解锁两个操作,因此当我们在加锁后,很容易忘记解锁,例如在unlock之前,触发了异常 或 return语句,此时就可能不会执行unlock
因此,为了保证unlock的执行,我们可以将其放在finally中
例如:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
//执行代码
}finally {
lock.unlock();
}
synchronzied使用时无需手动释放锁,ReentrantLock使用时需要手动释放,使用起来更加灵活,但也容易漏掉unlock
为什么有了synchronized后,还有ReentrantLock?
1. ReentrantLock提供了tryLock操作。lock直接进行加锁,当加锁失败时,就阻塞,而tryLock尝试进行加锁,若加锁失败,等待一定时间后,不阻塞,直接放弃加锁,返回 false,表示加锁失败
2. ReentrantLock提供了公平锁的实现(遵循“先来后到”规则,即等待时间长的线程先获取到锁)。通过队列来记录加锁线程的先后顺序,ReentrantLock默认是非公平锁,在构造方法中传入true 则可设置为公平锁
3. 更强大的唤醒机制。synchronized,搭配wait和notify来实现等待唤醒,每次随机唤醒一个等待的线程;而ReentrantLock,搭配Condition类来实现通知等待机制,可以更精确控制唤醒某个指定的线程
信号量Semaphore,用来表示“可用资源的个数”,其本质就是一个计数器
当申请资源时,可用资源数 -1(信号的P操作)
当释放资源时,可用资源数 +1(信号的V操作)
Semaphore中的加减计算操作都是原子的,可以在多线程环境下直接使用
信号量也是操作系统提供的机制,JVM对其对应的API进行封装,因此我们可用直接通过Java代码来调用这里的相关操作
Semaphore类中acquire()方法表示申请资源(P操作),release()方法表示释放资源(V操作)
例如:
public class Demo2 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);//有5个可用资源
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try{
System.out.println("申请资源");
semaphore.acquire();
System.out.println("获取到资源");
Thread.sleep(1000);
System.out.println("释放资源");
semaphore.release();
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
}
}
同时等待多个任务执行结束
例如,构造CountDownLatch实例latch,初始化n表示有n个任务需要完成。
在任务执行完毕时,调用countDown()方法,告知latch当前任务执行完毕,则CountDownLatch内部的计数器 -1
在主线程中调用latch.await(),阻塞等待所有任务都执行完毕(计数器为0)
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);//此时有10个任务需要完成
for (int i = 0; i < 10; i++) {
int id = i;
Thread t = new Thread(()->{
Random random = new Random();
int time = (random.nextInt(5) + 1) * 1000;//执行任务的时间
try {
Thread.sleep(time);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(id + "任务执行结束");
//告知latch执行结束
latch.countDown();
});
t.start();
}
//通过await来等待所有任务执行结束
latch.await();
System.out.println("所有任务都已执行完成");
}
}