[JAVAEE] 面试题(三) - Callable接口, ReentrantLock类, Semaphore信号量, CountDownLatch类
目录
一. Callable接口
1.1 Callable接口介绍
1.2 Callable接口 与 Runnable接口
1.3 Callable接口的使用
二. ReentrantLock类
2.1 lock() unlock() trylock()
2.2 synchronized关键字 与 ReentrantLock类的区别
三. Semaphore信号量
3.1 PV操作
3.2 二元信号量
四. CountDownLatch类
4.1 CountDownLatch类的使用
五. 总结
一. Callable接口
1.1 Callable接口介绍
Callable接口位于JUC(java.util.concurrent)包中. 被注释@FunctionalInterface, 表示Callable接口是函数式接口, Callable接口中有且仅有一个抽象方法.
1.2 Callable接口 与 Runnable接口
Runnable接口和Callable接口是并列关系, run方法与call方法并列.
Runnable中的run是任务的入口, 同理, Callable中的call也是任务的入口.
run没有返回值, call有返回值.
1.3 Callable接口的使用
Thread类没有提供构造方法来接受callable匿名类对象.
这时可以使用FutureTask对callable进行封装.
可以发现FutureTask类实现了RunnableFuture接口, RunnableFuture接口拓展了Runnable接口和Future接口. Thread类中提供了构造方法来接受FutureTask对象.FutureTask类中也提供了构造方法来接受Callable匿名对象.
所以, 综上所述, 可以通过FutureTask来找到Callable.
案例: 使用Callable创建线程, 并将原子类count中的值增加为50000.
private static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { for (int i = 0; i < 50000; i++) { count.getAndIncrement(); } return count.get(); } }; // Thread thread = new Thread(callable); FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); System.out.println(futureTask.get()); // get没有得到结果之前会一直阻塞, 获得结果为止. }
二. ReentrantLock类
2.1 lock() unlock() trylock()
lock(): 加锁
unlock(): 解锁 (需要注意unlock不被调用的问题, 也就是线程提前终止)
trylock(): 不会阻塞, 加锁成功,返回true; 加锁失败, 返回false. 调用者根据返回值来决定下一步该做什么.
案例: 使用reentrantLock加锁, 来实现线程安全.
static int count = 0; public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(true); Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { reentrantLock.lock(); try { count++; } finally { reentrantLock.unlock(); } } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { reentrantLock.lock(); try { count++; } finally { reentrantLock.unlock(); } } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); }
2.2 synchronized关键字 与 ReentrantLock类的区别
1. synchronized是关键字(是JVM内部通过C++实现的), ReentrantLock是JUC中的类.
2. synchronized通过代码块来控制加锁和解锁, ReentrantLock通过lock, trylock, unlock来控制加锁和解锁.
3. synchronized是非公平锁, ReentrantLock默认是非公平锁, 可以设置为公平锁.
4. ReentrantLock除了提供lock, unlock外, 还提供了trylock. (不会阻塞, 加锁成功返回true, 加锁失败返回false, 调用者根据返回值来决定下一步做什么)
5. ReentrantLock搭配的等待通知机制, 是Condition类, 比wait/notify功能更强大.
三. Semaphore信号量
Semaphore(信号量), 描述了某种可用资源的个数.
3.1 PV操作
申请一个资源, 计数器就会 +1. acquire
释放一个资源, 计数器就会 -1. release
注意: 可用资源个数为0时, 再继续申请, 就会阻塞.
3.2 二元信号量
可用资源个数为1, 申请资源相当于加锁, 释放资源相当于解锁.
(利用了可用资源个数为0时, 再继续申请, 就会阻塞).
static int count = 0; public static void main(String[] args) throws InterruptedException{ Semaphore semaphore = new Semaphore(1); Thread thread1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { try { semaphore.acquire(); } catch (InterruptedException e) { throw new RuntimeException(e); } count++; semaphore.release(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { try { semaphore.acquire(); // 可用资源个数为0时, 再继续申请, 阻塞. } catch (InterruptedException e) { throw new RuntimeException(e); } count++; semaphore.release(); } });
四. CountDownLatch类
使用多线程, 经常将一个大任务拆分成多个小任务, 从而提升程序的运行效率.
4.1 CountDownLatch类的使用
1. 构造方法指定参数, 表示拆分成了几个小任务.
2. 每个小任务执行完毕后, 都调用一次countDown方法(当调用2次时, 说明小任务都执行完毕)
3. 主线程中调用 await 方法, 所有任务执行完后, 结束阻塞.
示例:
五. 总结
1. Callable接口和Runnable接口是并列关系, call方法与run方法都是任务入口, call有返回值, run没有返回值.
2. 通过FutureTask类找到Callable匿名对象.(线程和任务解耦和).
3. ReentrantLock类中的常用方法, lock, unlock, trylock.
4. ReentrantLock类与synchronzied关键字的区别.
5. Semaphore信号量, 描述某种可用资源的个数.
6. 二元信号量, 模拟实现锁的效果.
7. CountDownLatch类, 描述小任务的个数, 决定何时结束阻塞.