1.callable接口
显然它也是一个函数式接口,这意味着我们可以通过Lambda表达式创建简化写法
对比Runnable接口:
1.call()方法多了一个可以抛出的异常,而Exception是所有异常的父类,因此可以抛出所有的异常,而run()方法只能在方法内部解决。
2.call()多了一个返回值。
我们在应用中介绍它的优势:
通过callable定义任务
1.定义一个类,实现callable接口
2.通过匿名内部类的方式
3.通过Lambda表达式的方式
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("执行任务...");
int sum = 0;
//完成累加操作
for (int i = 1; i <= 10; i++) {
sum +=i;
}
TimeUnit.SECONDS.sleep(1);
System.out.println("运算结束..."+"sum = " + sum);
return sum;
}
};
那么如何像Runnable一样搭配Thread?
//搭配Future使用,获取Callable的执行结果
FutureTask<Integer> futureTask = new FutureTask<>(callable);
看源码知它继承自Runnable
如何获取callable的结果呢?(call()拥有返回值的好处)
注意此方法会一直阻塞等待到任务执行完毕
注意此处本应抛出异常,如果在Run方法中就必须用try环绕
如果是其他业务异常的话就需要当场处理,但是call方法中就可以抛出
面试题:Runnable与Callable的区别
1.Callable要实现call(),且有返回值,Runnable实现的run()但没有返回值2. Callable的call()可以抛出异常,Runnable的run()不能抛出异常
3.Callable配合FutrueTask一起使用,通过futureTask.get()方法获取call()的结果
4.两都是描述线程任务的接口
面试题:创建线程有几种方式?
1.继承Thread类,实现run()方法
2.实现Runnable接口,并且实现run()方法
3.实现Callable接口,并且实现call()方法
4.通过创建线程池,并提交任务
2.ReentrantLock
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
//加锁
lock.lock();
//被上锁的代码
//释放锁
lock.unlock();
//尝试获取锁,如果获取到锁返回true,执行加锁的逻辑,如果获取不到锁返回false,执行不加锁
的逻辑
lock.tryLock();
//尝试加锁,并指定等待时间
lock.tryLock(1, TimeUnit.SECONDS);
//创建一个公平锁 获取锁的方式遵循先来后到
ReentrantLock lock1 = new ReentrantLock(true);
//创建一个读写锁
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//创建一个读锁
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
//创建一个写锁
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
}
多个线程可以同时加读锁,也就是真正串行执行
1.当上面的读锁线程读共享变量时,下面的写锁线程就加写锁就要等待 ,因为写锁是排他锁,不能与其他锁共存。
2.只有当上面的线程全部完成读操作后,下面的写操作才可以执行。
3.加了写锁之后,其他线程也不能读了,必须等写操作完成,释放完写锁资源才能加读锁。
写锁与读锁不可共存
ReentrantLock与synchronized的区别
1.
synchronized是一个关键字,在JVM内部实现(大概率是C++实现)
ReentrantLock是标准库中的一个类,在JVM外部实现(Java代码实现)
2.
synchronized使用时不需要手动释放锁资源
ReentrantLock需要手动释放,更加灵活,但也容易忘记释放锁资源
3.
synchronized申请锁资源失败后,死等
Reentrant Lock可以通过Try Lock等一段时间后放弃
4.
synchronized是一个非公平锁
Reentrant Lock默认是一个非公平锁,可以通过构造时传入一个true开启公平锁模式
5.
更强大的唤醒机制.
synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是⼀个
随机等待的线程.
ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定
的线程.
显然这个类底层是一个链表,并且内部装的是Thread线程,那么它的等待操作就是等待线程入队,唤醒就是出队操作,并且,从源码中并没有具体的链表长度,这意味着来链表尽可能大。
3.Semaphore
信号量, ⽤来表⽰ "可⽤资源的个数". 本质上就是⼀个计数器
举一个例子理解:
停车场:
停车场外面通常会一个显示牌,牌子上会显示当前停车场中车位的可用个数进入停车场一辆车之个,个数就减1,出场一辆车之后个数 加1如果停车场的车位占满了,那么显示牌上就显示车位已满,这时外面的车就需要阻塞等待当一辆车出场之后,个数加1了,意味着释放了资源,外面等待的车就可以进场
//创建信号量变量
Semaphore semaphore = new Semaphore(5);
// 申请资源,如果有可用资源就申请,如果没有只有阻塞等待
semaphore.acquire();
// 释放资源
semaphore.release();
Semaphore semaphore = new Semaphore(5);
// 定义线程的任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始申请资源...");
try {
// 申请资源, 可用资源数减1
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "====== 已经申请到资源 ======");
// 处理业务逻辑, 用休眠来模拟
TimeUnit.SECONDS.sleep(1);
// 释放资源, 可用资源数加1
semaphore.release();
System.out.println(Thread.currentThread().getName() + "***** 释放资源");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建线程执行任务, 20 有20个线程需要执行任务
for (int i = 0; i < 20; i++) {
// 创建线程并指定任务
Thread thread = new Thread(runnable);
// 启动线程
thread.start();
}
通过信号量可以限制系统中并发执行的线程个数
4.CountDownLatch
//每个任务执⾏完毕, 都调⽤ latch.countDown() .
//在 CountDownLatch 内部的计数器同时⾃减
count.countDown();
//主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕. 相当于计数器为 0 了
count.await();
CountDownLatch 可以等某个事情完成之后,(也就是等全部的线程执行完毕后)再去执行其他任务
应用场景:
比如可以把一个大的文件分成许多许多的小模块,对每个小模块分别进行下载,当全部的小模块下载好之后,将这些下载好的小模块合并成大的文件,这样就提高了下载速度 。