接上篇 juc–并发编程的核心问题总结①
一、读写锁ReadWriteLock
ReadWriteLock维护了一对关联的locks,一个用于只读操作,一个用于写入。
读的时候可以有多个阅读线程同时进行,写的时候只能有一个线程去写。
写锁:独占锁
读锁:共享锁
写线程不能与任何线程共存,只有读线程和读线程可以共存
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();
public class demo9 {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写入
for (int i = 1; i < 6; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp+"",20);
}).start();
}
//读
for (int i = 1; i < 4; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp+"");
}).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写 一个线程写
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在写");
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写完了");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//读 多个线程读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在读");
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读完了");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
}
二、阻塞队列BlockingQueue
写入时如果队列满了就必须要阻塞等待,读取时如果队列是空的必须要阻塞等待。
BlockingQueue有4种不同的API。抛出异常;有返回值,不抛出异常;阻塞一直等待;阻塞等待,超时退出。
1. 抛出异常
public class demo10 {
public static void main(String[] args) {
test1();
}
public static void test1(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3
System.out.println(blockingQueue.add("a"));//true
System.out.println(blockingQueue.add("b"));//true
System.out.println(blockingQueue.add("c"));//true
System.out.println(blockingQueue.element());//查看队首元素 a
//Exception in thread "main" java.lang.IllegalStateException: Queue full 队列已满
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());//a
System.out.println(blockingQueue.remove());//b
System.out.println(blockingQueue.remove());//c
//Exception in thread "main" java.util.NoSuchElementException 队列为空
//System.out.println(blockingQueue.remove());
}
}
2. 有返回值,不抛出异常
public static void test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3
System.out.println(blockingQueue.offer("a"));//true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d"));//false 不抛出异常
System.out.println(blockingQueue.peek());//查看队首元素 a
System.out.println(blockingQueue.poll());//a
System.out.println(blockingQueue.poll());//b
System.out.println(blockingQueue.poll());//c
System.out.println(blockingQueue.poll());//null 不抛出异常
}
3. 阻塞一直等待
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//队列已满,阻塞,一直等待
blockingQueue.put("d");
System.out.println(blockingQueue.take());//a
System.out.println(blockingQueue.take());//b
System.out.println(blockingQueue.take());//c
//队列已空,阻塞,一直等待
System.out.println(blockingQueue.take());
}
4. 阻塞等待,超时退出
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//队列已满,阻塞,等待2s后若还是阻塞就退出
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());//a
System.out.println(blockingQueue.poll());//b
System.out.println(blockingQueue.poll());//c
//队列已空,阻塞,等待2s后若还是阻塞就退出
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
5. (特殊)同步队列SynchronousQueue
SynchronousQueue不存储元素,入队一个元素必须等待它出队才能再入队。(相当于容量为1)
public class demo11 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
blockingQueue.put("2");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "get 1---"+blockingQueue.take());
System.out.println(Thread.currentThread().getName() + "get 2---"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
三*、线程池
线程池的好处:(实现线程复用,控制最大并发数和管理线程)
- 降低资源消耗 :通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度 :当任务到达时,可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性。
线程池有3大方法,7大参数,4种拒绝策略
1. 三大方法
Executors.newSingleThreadExecutor();//创建一个单线程,因此所有提交的任务是顺序执行
public class PoolDemo {
public static void main(String[] args) {
//创建一个单线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 7; i++) {
//使用线程池创建线程 {}中Runnable
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程池
threadPool.shutdown();
}
}
}
ExecutorService threadPool = Executors.newFixedThreadPool(int nThreads); //创建一个线程池,大小固定为nThreads个线程,如果没有任务执行,那么线程会一直等待
public class PoolDemo {
public static void main(String[] args) {
//创建一个线程池,大小固定为5个线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 7; i++) {
//使用线程池创建线程 {}中Runnable
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程池
threadPool.shutdown();
}
}
}
ExecutorService threadPool =Executors.newCachedThreadPool();//创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public class PoolDemo {
public static void main(String[] args) {
//创建一个可缓存的线程池
ExecutorService threadPool =Executors.newCachedThreadPool();
try {
for (int i = 0; i < 7; i++) {
//使用线程池创建线程 {}中Runnable
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程池
threadPool.shutdown();
}
}
}
2. 七大参数
由三大方法的源码可知,本质是ThreadPoolExecutor():
ThreadPoolExecutor()源码:
阿里巴巴java开发手册:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和singleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2) CachedThreadPool和scheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
参数举例:
有一家商店,该商店有10个座位,和3个候客区。其中有3个座位是一直开放的,另外7个座位有需要才开放。
开始时,只开放了前三个座位,只有两位客人,3号座位空闲,候客区空闲。
过了一会,商店涌入了一大批客人,于是商店开放了后面4个座位,此时候客区也坐满了。
过了两分钟,又进来了一位客人,但是已经没有空余座位了,候客区也满了,这位客人就只能选择等待或者离开。
若一段时间后客人都走了,后面4个座位1个小时都没有人来,那么就不再对外开放这几个座位(超时释放)。
// 自定义一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,// 核心线程
7, // 最大线程
3, // 等待3s
TimeUnit.SECONDS,// 单位 s
new LinkedBlockingQueue<>(3),// 阻塞队列 候客区大小
Executors.defaultThreadFactory(),// 线程工厂 一般不变
new ThreadPoolExecutor.AbortPolicy()// 默认拒绝策略 抛出 RejectedExecutionException异常
);
3. 四种拒绝策略
// 默认拒绝策略 抛出RejectedExecutionException异常
ThreadPoolExecutor.AbortPolicy();
// 它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务(由调用线程处理该任务)
ThreadPoolExecutor.CallerRunsPolicy());
// 丢弃队列中最老的任务,并尝试提交新的任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy());
// 队列满了就丢弃任务,不会抛出异常
ThreadPoolExecutor.DiscardPolicy();
4. 如何定义线程池的最大线程数
CPU密集型
CPU密集型也是指计算密集型,大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。
该类型的任务需要进行大量的计算,主要消耗CPU资源。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,CPU集型任务同时进行的数量应当等于CPU的核心数。
IO密集型
IO密集型任务指任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较少,其消耗的主要资源为IO。IO密集型的线程数为你判断出的IO密集型线程的两倍。
四*、四大函数式接口
基础:
java Lambda表达式
java 函数式接口
java Stream流
详见:java 函数式接口