概述
|
JavaEE
JavaEE——常见的锁策略
JavaEE——CAS
目录
- JUC
- 1. Callable 接口
- 2. ReentrantLock
- 3. 原子类
- 4. 线程池
- 5. 信号量 Semaphore
- 6. CountDownLatch
JUC
JUC 全称 java.util.concurrent
1. Callable 接口
类似于 Runnable. Runnable 描述的任务, 不带返回值. Callable 描述的任务是带返回值的.
示例: 创建线程, 通过线程来计算 1 + 2 + 3 + . . . + 1000
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用 callable 定义一个任务
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 的构造方法, 不能直接传 callable , 还需要一个中间的类
Thread t = new Thread(futureTask);
t.start();
//获取线程的计算结果
//get 方法会阻塞, 直到 call 方法计算完毕, get 才会返回
System.out.println(futureTask.get());
}
}
总结: 线程创建的方式
- 继承 Thread
- 实现 Runnable
- 使用 lambda
- 使用线程池
- 使用 Callable
2. ReentrantLock
可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.
核心用法:
- lock( ): 加锁, 如果获取不到锁就死等
- tryLock(超时时间): 加锁, 如果获取不到锁, 等待一定时间之后就放弃加锁
- unlock( ): 解锁
小缺点
import java.util.concurrent.locks.ReentrantLock;
public class Demo1 {
public static void main(String[] args) {
ReentrantLock locker = new ReentrantLock();
//加锁
locker.lock();
//解锁
locker.unlock();
}
}
在上述代码中, 如果在 locker.lock();
和 locker.unlock();
之间, 出现了 return, 或者有异常, 就可能导致 unlock
执行不了.
解决方案: 使用try finally
import java.util.concurrent.locks.ReentrantLock;
public class Demo1 {
public static void main(String[] args) {
ReentrantLock locker = new ReentrantLock();
try{
//加锁
locker.lock();
}finally {
//解锁
locker.unlock();
}
}
}
优势
ReentrantLock 有一些特定的功能, 是 sychronized 做不到的.
-
tryLock
试试看能不能加上锁. 成功了, 就加锁成功. 试失败了, 就放弃. 并且还可以指定加锁的等待超时时间. -
ReentrantLock
可以实现 公平锁. (默认是非公平的), 构造的时候,传入一个简单的参数, 就成了公平锁.ReentrantLock locker = new ReentrantLock(true);
-
sychornized 是搭配
wait/ notify
实现等待通知机制的, 唤醒操作是一个随机的过程.ReentrantLock 是搭配
Condition
类实现的, 可以指定唤醒哪一个等待的线程.
3. 原子类
原子类的底层, 是基于 CAS 实现的, Java 里面已经封装好了, 可以直接来使用.
使用原子类, 最常见的场景就是多线程计数, 写了个服务器, 服务器一共有多少并发量
, 就可以通过这样的原子变量来累加.
示例
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) throws InterruptedException {
AtomicInteger count = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
// 相当于 count++
count.getAndIncrement();
/*// 相当于 ++count
count.incrementAndGet();
// 相当于 count--
count.getAndDecrement();
// 相当于 count--
count.decrementAndGet();*/
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
// 相当于 count++
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
运行结果展示
4. 线程池
JavaEE——No.2 多线程案例(内含线程池)
JavaEE——线程小知识(线程和线程池的好处)
5. 信号量 Semaphore
信号量的基本操作是两个:
- P 操作, 申请一个资源
- V操作, 释放一个资源
信号量本身是一个计数器
, 表示可用资源的个数.
- P 操作申请一个资源, 可用资源就 -1
- V 操作释放一个资源, 可用资源就 +1
- 当计数为 0 的时候, 继续 P 操作, 就会产生阻塞等待, 阻塞等待到其他线程 V 操作了为止.
信号量可以视为是一个更广义的锁, 锁就是一个特殊的信号量 (可用资源只有 1 的信号量)
我们把互斥锁也看做是计数为 1 的信号量.(取值只有 1 和 0, 也叫做 二元信号量)
Java 标注库提供了 Semaphore
, 这个类, 其实就是把 操作系统 提供的信号量封装了一下.
示例
import java.util.concurrent.Semaphore;
public class Test {
public static void main(String[] args) throws InterruptedException {
// 构造的时候需要指定初始值, 计数器的初始值, 表示有几个可用资源
Semaphore semaphore = new Semaphore(4);
// P 操作, 申请资源, 计数器 - 1
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
// V 操作, 申请资源, 计数器 + 1
semaphore.release();
System.out.println("V 操作");
}
}
上述代码中, 我们在只有 4
个可用资源的前提下, 进行了 5
次 P 操作, 会发生什么呢?
我们发现, 进行了 4次 P操作
之后, 就开始阻塞等待, 这个阻塞会一直阻塞下去, 直到有人进行 V操作.
# 注意 #
当需求中, 有多个可用资源的时候, 要记得使用信号量.
6. CountDownLatch
同时等待 N 个任务执行结束.
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才结束比赛.
使用 CountDownLatch
就是类似的效果, 使用时先设置有几个选手, 选手撞线的时候, 就调用一下 CountDownLatch
方法, 当撞线次数达到了选手的个数, 就认为比赛结束了.
示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatch1 {
public static void main(String[] args) throws InterruptedException {
// 有 10 个选手参加了比赛
CountDownLatch countDownLatch = new CountDownLatch(10);
for(int i = 0; i < 10; i++){
//创建 10 个线程来执行一批任务
Thread t = new Thread(() -> {
System.out.println("选手出发! " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("选手到达! " + Thread.currentThread().getName());
//撞线
countDownLatch.countDown();
});
t.start();
}
//await 是进行阻塞等待, 会等到所有选手都撞线之后, 才结束阻塞
countDownLatch.await();
System.out.println("比赛结束! ");
}
}
运行结果展示
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!
最后
以上就是伶俐白猫为你收集整理的JavaEE——JUCJUC的全部内容,希望文章能够帮你解决JavaEE——JUCJUC所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复