JUC(java.util.concurrent)的常见类
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
一、JUC常见类
concurrent代表了并发这个包下为我们提供了并发编程(多线程)相关的组件.
Callable 接口
我们的Callable接口和Runnable是一样的但也有一些区别:
Runnable: 用来描述一个任务描述的任务是没有返回值的。
Callable: 用来描述一个任务描述的任务是有返回值的。
所以我们在一些场景需要线程为我们计算出某个结果的话就可以使用Callable。
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null;
}
};
}
当我们直接把callable传入到Thread的构造方法里报错了没有该构造方法这里需要套上一层辅助类。
不能直接放进去也很简单因为我们的Callable是带有返回值的可能有多个线程去执行同一个callable的任务而且啥时候执行完并不确定所以我们这里无法获取到返回值FutureTask就可以负责这个等待返回值回来的工作。
然后我们的FutureTask为我们提供了一个get()方法用来获取任务的返回值。
我们来使用Callable接口实现以下1 + 2 + … + 100
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 <= 100; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
Integer ret = futureTask.get();
System.out.println(ret);
}
ReentrantLock
ReentrantLock是标准库为我们提供的另一种锁顾名思义。该所也是可重入锁。
我们的Synchronized也是我们标准库提供的锁那么它们有什么区别呢?
Synchronized: 是直接基于代码块进行加锁解锁的。
ReentrantLock是使用了lock()方法和unlock()方法进行加锁解锁的。
方法 | 作用 |
---|---|
lock() | 加锁获取不到就死等 |
trylock(time 超时时间) | 加锁如果一定时间内没有获取到就放弃 |
unlock() | 解锁 |
我们的ReentrantLock在使用起来可能有一些需要注意的事项
我们发现系统这里提示我们lock操作必须搭配try代码块unlock操作必须搭配finally代码块进行操作。
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
try {
reentrantLock.lock();//加锁
} finally {
reentrantLock.unlock();//解锁
}
}
上述是我们ReentrantLock的劣势当然我们ReentrantLock也是有一些优势的。
1.我们的ReentrantKLock提供了公平锁版本的实现我们的Synchronized只实现了非公平锁。
2. 我们Synchronized尝试加锁如果锁已经被占有就进行阻塞等待(死等)ReentrantLock提供了更灵活的获取锁的方式: trylock()
方法 | 作用 |
---|---|
trylock() | 无参数能加锁就加加不上就放弃 |
trylock(time) | 有参数超过指定时间加不上锁就放弃 |
3.ReentrantLock提供了一个更方便的等待通知机制Synchronized搭配的是wait,notify,当我们notify的时候是随即唤醒一个wait状态的线程。ReentrantLock搭配一个Condition类进行唤醒的时候可以唤醒指定线程。
Semaphore(信号量)
Semaphore(信号量)信号量本质就是一个计数操作描述"可用资源的个数"这里设计两个操作。
P操作申请一个可用资源计数器-1
V操作释放一个可用资源计数器+1
这里我们的信号量可以搭配着停车场来理解
我们的停车场的大牌子上就写着当前剩余车位xx个当有车进去就相当于进行了一次P操作计数器-1当有车从停车场出来就相当于进行了一次V操作计数器+1当我们的停车场的剩余车位为0时也就是计数器为0时如果有车想停车要么在这里等要么就去其他停车场。
我们的Semaphore同样提供了两个方法
方法 | 作用 |
---|---|
acquire | 申请资源 |
release | 释放资源 |
我们在这里设计一个场景我们图书馆一共有5本《算法导论》但有10个同学去借
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("我来借书了");
semaphore.acquire();
System.out.println("我拿到算法导论了!");
Thread.sleep(500); //借阅时间
System.out.println("我还书了");
semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
for (int i = 0; i < 10; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
我们的锁可以认为是一个计数器为1的信号量)锁是信号量的特殊情况信号量是锁的一般表达。
实际开发中锁的出场频率是最高的但也是有一些特殊的场景会用到信号量。
CountDownLatch
CountDownLatch是一个小组件用于处理特殊场景下的。
就类似一场跑步比赛开始时间是明确的但结束时间是不确定的只有所有选手冲线了才算结束。
像这种场景就引入了CountDownLatch.
方法 | 作用 |
---|---|
CountDownLatch(人数) | 在构造的时候传入一个计数(参赛选手的个数) |
await() | 等待所有线程执行结束 |
countDown() | 表示选手冲线 |
public static int person;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 1000));
System.out.println(person + "号选手冲线了");
person++;
latch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
for (int i = 0; i < 5; i++) {
Thread t = new Thread(runnable);
t.start();
}
latch.await(); //等5位选手冲线
System.out.println("比赛结束");
}
在我们下载一个大型文件的时候就可以用到CountDownLatch,比如一款游戏6个G我们多线程下载就可以把这个文件切分成许多小文件多个线程同时下载我们多线程下载是充分利用了带宽(下载是IO操作不太涉及CPU)此处就可以用到CountDownLatch来判断整体是否下载完毕。