赞
踩
concurrent:并发(多线程)
Callable
是一个 interface
。也是一种创建多线程的方式,相当于把线程封装了一个 “返回值”。方便程序员借助多线程的方式计算结果。
Runnable
能表示一个任务(run方法),返回void
Callable
也能表示一个任务(call方法),返回一个具体的值,类型可以通过泛型参数来指定(Object)
如果进行多线程操作,如果你关心多线程执行的过程,使用Runnable
,比如线程池,定时器,就是用的Runnable
只关心过程
如果进行多线程操作,如果你关心多线程的计算结果,使用Callable
,比如通过多线程的方式计算一个公式,计算1+2+…+1000
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000
不使用 Callable 版本
static class Result { public int sum = 0; public Object lock = new Object(); } public static void main(String[] args) throws InterruptedException { Result result = new Result(); Thread t = new Thread() { @Override public void run() { int sum = 0; for (int i = 1; i <= 1000; i++) { sum += i; } synchronized (result.lock) { result.sum = sum; result.lock.notify(); } } }; t.start(); synchronized (result.lock) { while (result.sum == 0) { result.lock.wait(); } System.out.println(result.sum); } }
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂, 容易出错.
使用 Callable 版本
futureTask.get()
能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.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 t = new Thread(futureTask);//使用Callable不能作为Thread的构造方法参数,是借助FutureTask
t.start();
int result = futureTask.get();//通过FutureTask获取Callable的call方法的结果,get类似join一样,如果call方法没算完会阻塞等待
System.out.println(result);
可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了.
理解Callable
Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务.
Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
FutureTask 就可以负责这个等待结果出来的工作.
理解FutureTask
想象去吃麻辣烫. 当餐点好后, 后厨就开始做了. 同时前台会给你一张 “小票” . 这个小票就是FutureTask. 后面我们可以随时凭这张小票去查看自己的这份麻辣烫做出来了没
目前为止学到的创建线程的方式:
其中1、2、5搭配匿名内部类使用
可重入互斥锁,和 synchronized 定位类似,都是用来实现互斥效果,保证线程安全
ReentrantLock的用法:
ReentrantLock具有一些特点,是synchronized 不具备的功能:
对于lock操作,如果加锁不成功,就会阻塞等待(死等)
对于tryLock,如果加锁失败,直接返回false/也可以设定等待时间
unlock()
,通常使用finally
来执行ReentrantLock 和 synchronized 的区别:
如何选择使用哪个锁?
原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个
以 AtomicInteger 举例,常见方法有
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;
应用场景:
播放量、点赞量…
统计出现错误的请求数目、统计收到的请求数目(衡量服务器的压力)、统计每个请求的响应时间=>平均响应时间(衡量服务器的运行效率)…
通过上述统计内容,实现监控服务器,获取/统计/展示/报警
虽然创建销毁线程比创建销毁进程更轻量,但是在频繁创建销毁线程的时候还是会比较低效.
线程池就是为了解决这个问题。如果某个线程不再使用了,并不是真正把线程释放,而是放到一个 “池子” 中,下次如果需要用到线程就直接从池子中取,不必通过系统来创建了。
代码示例:
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors 创建线程池的几种方式
Executors 本质上是 ThreadPoolExecutor 类的封装.
ThreadPoolExecutor 提供了更多的可选参数,可以进一步细化线程池行为的设定.
ThreadPoolExecutor 的构造方法
信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器,描述的是当前这个线程,是否“有临界资源可以用”
临界资源:多个线程/进程等并发执行的实体可以公共使用到的资源(多个线程修改同一个资源,这个变量就可以认为是临界资源)
理解信号量
可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.
当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)
当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.
这个阻塞等待的过程有点像锁是吧。
锁,本质上就是一个特殊的信号量(里面的数值,非0即1,二元信号量)
信号量要比锁更广义,不仅可以描述一个资源,还可以描述N个资源。
但还是锁用的更多一些
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
针对特定场景一个组件
同时等待 N 个任务执行结束
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
原来的集合类,大部分都不是线程安全的
Vector,Stack,HashTable,是线程安全的(不建议用),其他的集合类不是线程安全的
Vector、HashTable是上古时期搞出来的集合类
加了锁不一定就线程安全了,不加锁也不一定就线程不安全了
Collections.synchronizedList(new ArrayList)
synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List.
synchronizedList 的关键操作上都带有 synchronized
相当于让ArrayList像Vector一样使用
CopyOnWrite容器即写时复制的容器。
- 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,
- 添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会
添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
优点:
在读多写少的场景下, 性能很高, 不需要加锁竞争.
缺点:
- 占用内存较多.
- 新写的数据不能被第一时间读取到.
基于数组实现的阻塞队列
基于链表实现的阻塞队列
基于堆实现的带优先级的阻塞队列
最多只包含一个元素的阻塞队列
HashMap 本身不是线程安全的
在多线程环境下使用哈希表可以使用:
只是简单的把关键方法加上了 synchronized 关键字
public synchronized V put(K key, V value) {
public synchronized V get(Object key) {
这相当于直接针对 Hashtable 对象本身加锁
相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。