JUC是:java.util.concurrent这个包名的缩写。它里面包含了与并发相关,即与多线程相关的很多东西。我们下面就来介绍这些东西。
Callable接口类似与Runnable接口:
Runnable接口:描述的任务是不带返回值的。
callable接口:描述的任务是带返回值的,存在的意义就是让我们获取到结果
让我们通过下面的代码来仔细体会一下不同
// 使用Runnable来计算 1+2+.....+1000
static class Result{
public int sum;
public Object locker = new Object();
}
public static void main(String[] args) {
Result result = new Result();
//创建一个专门的线程来求和
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 1000; i++){
result.sum ++;
}
synchronized (result.locker){
result.locker.notify();
}
}
};
thread.start();
// 必须得等thread线程执行完了再打印
synchronized (result.locker) {
if (result.sum == 0){
try {
result.locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(result.sum);
}
}
// 使用callable来计算1+2+....+1000
public static void main(String[] args) {
//使用callable来定义一个任务
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 1000; i++){
sum++;
}
return sum;
}
};
// 用来接受callable任务作为参数
FutureTask<Integer> futureTask = new FutureTask<>(callable);
//Thread 里的参数没有callable类型 只有futuretask类型 所以需要创建一个futuretask类型来过渡
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
ReentrantLock,也是可重入锁。它的出现是为了补充synchronized(可重入锁)无法实现的一些操作。
它一共有三个核心的方法:
public static void main(String[] args) {
// 和synchronized相比有三个优势 两个不同
ReentrantLock reentrantLock = new ReentrantLock();
try {
reentrantLock.lock();
}finally {
//当在 lock和unlock 之间出现异常时 unlock就无法执行到 需要用到finally来必须执行
//synchronized不存在这个问题 因为它只要出了代码块就一定会解锁
reentrantLock.unlock();
}
}
ReentrantLock reentrantLock = new ReentrantLock(true);
原子类的底层是基于CAS实现的,使用原子类最常见的场景就是多线程计数。
CAS操作前面已经非常详细的介绍过,点击此处可以查看浏览
//原子类 多用于计数
//count.getAndIncrement = count++
//count.incrementAndGer = ++count
//count.getAndDecrement = count--
//count.decrementAndGer = --count
public static void main(String[] args) {
AtomicInteger count = new AtomicInteger();
Thread thread = new Thread(() -> {
for (int i = 0; i < 50000; i++){
//相当与count++;
count.getAndIncrement();
}
});
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 50000; i++){
//相当于count++;
count.getAndIncrement();
}
});
thread.start();
thread1.start();
try {
thread.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count.get());
}
前面已经非常详细的介绍过,点击此处可以查看浏览
信号量的基本操作有两个:
P操作:申请一个资源
V操作:释放一个资源
信号量本身是一个计数器,表示可用资源的个数:
P操作申请一个资源,可用资源的个数就-1
V操作释放一个资源,可用资源的个数就+1
当计数为0时,继续进行P操作就会阻塞,直到其他线程执行V操作释放资源
// 信号量可以看成更广义的锁 锁就是一个信号量 可用资源数只有1
public static void main(String[] args) throws InterruptedException {
// 需要指定初始值 表示可用资源的个数
Semaphore semaphore = new Semaphore(4);
semaphore.acquire();
System.out.println("申请资源");
semaphore.acquire();
System.out.println("申请资源");
semaphore.acquire();
System.out.println("申请资源");
semaphore.release();
System.out.println("释放资源");
semaphore.release();
System.out.println("释放资源");
}
CountDownLatch使用的效果:类似于一个跑步比赛,当最后一个选手到达终点就结束。
使用CountDownLatch的时候,首先要设置一下有几个选手参赛,每个选手撞线了就调用一下countDown方法,当撞线次数达到选手的个数时,比赛就结束。
放到程序中理解:
比如:要下载一个很大的文件,把文件分成多部分分别下载。使用多线程执行下载任务,每个线程下载一部分,当所有的线程都下载完毕,整个线程就下载完毕了。
//CountDownLatch
public static void main(String[] args) {
// 设置任务的个数
CountDownLatch downLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++){
Thread thread = new Thread(() -> {
System.out.println("开始任务" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务结束" + Thread.currentThread().getName());
downLatch.countDown();
});
thread.start();
}
try {
downLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部任务都结束");
}