【JUC 二】Callable Runnable 同步辅助类 读写锁 阻塞队列 线程池
7、Callable()
7.1、Callable 与 Runnable
| 比较 | Callable | Runnable |
|---|---|---|
| 类型 | 接口 (函数式接口,可用lambda) | 接口 (函数式接口,可用lambda) |
| 包 | java.util.concurrent | java.lang |
| 方法 | call() | run() |
| 返回值 | 有返回值。类型与泛型的类型一致 | 没有返回值。void |
| 导常 | 可以抛出异常 | 不能抛出被检查的异常 |


7.2、Thread 中使用 Callable
问题
-
Thread 不能直接使用 Callable 的实现类
Thread 构造器中不能接受 Callable 的实现类,但可以接受 Runnable 的实现类

思路
让 Callable 和 Runnable 建立联系,通过 Runnable 做桥梁,让 Thread 可以使用 Callable




8、同步辅助类 (必会)

8.1、CountDownLatch
同步计数器(减法)
原理:
countDownLatch.countDown(); //数量减1
countDownLatch.await();// 等待计数器归零,然后再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await(),就会被唤醒,继续执行

- 示例
厂区门卫
当所有人都离开后,再关门
package auxiliaryClass;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
/**
* @author ajun
* Date 2021/7/5
* @version 1.0
* 减法计算器
*/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//定义计算器
// 倒计时总数是6, 必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " : 走了!");
countDownLatch.countDown();//计算器减1
return 1000;
}), String.valueOf(i)).start();
}
countDownLatch.await();// 等待计数器归零,然后再向下执行
System.out.println("关门!");
}
}

8.2、CyclicBarrier
同步计数器(加法)
cyclic 循环
barrier 栅栏


- 示例
观光车:每达到5人时发车
package auxiliaryClass;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* @author ajun
* Date 2021/7/5
* @version 1.0
* 循环栅栏
* 观光车:每达到5人时发车
*/
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
//观光车:每达到5人时发车
CyclicBarrier bus = new CyclicBarrier(5,() -> {System.out.println("可以发车了");});
for (int i = 1; i <= 50; i++) {
TimeUnit.SECONDS.sleep(1);//乘客到来的时间间隔
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 号乘客来了");
try {
bus.await();//等待。达到5人就通知发车,否则就等待。
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}

8.3、Semaphore
信号量、信号灯
原理:
semaphore.acquire(); //获取信号量,假设已经满了,等待信号量可用时被唤醒
semaphore.release();//释放信号量
作用:
多个共享资源互斥的使用!
并发限流,控制最大的线程数

-
示例
停车场
10个位,30辆车
package auxiliaryClass;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author ajun
* Date 2021/7/5
* @version 1.0
* 信号灯:抢车位
*/
public class SemaphoreTest {
private static final int MAX_PARKING = 10;//最大停车位
private static int cars = 0;//当前停车数量
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(MAX_PARKING);//停车场
Random random = new Random();//计算随机停车时间
for (int i = 1; i <= 30; i++) {//30辆车
TimeUnit.SECONDS.sleep(1);//车辆进场间隔
int t = 10 + random.nextInt(5);//随机停车时间 10 - 15
new Thread(() -> {
try {
semaphore.acquire();//获得车位。如果没有车位,就阻塞等待。
System.out.println(Thread.currentThread().getName() + "号车进场!");
carsNum(1);//车辆数加1
TimeUnit.SECONDS.sleep(t);//停车时间
System.out.println(Thread.currentThread().getName() + "号车离场了!停了 " + t + " 秒钟");
carsNum(-1);//车辆数减1
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();//释放车位
}
}, String.valueOf(i)).start();
}
}
//统计当前停车数量和剩余车位
public static void carsNum(int n) {
cars = cars + n;//当前车辆数
int pN = MAX_PARKING - cars;//剩余车位
System.out.println("\n【当前有 " + cars + " 辆车!剩余车位 " + pN + "个】\n");
}
}

9、读写锁
9.1、简介
ReadWriteLock
包括 读锁 和 写锁
- 读锁
- 也叫 共享锁;一次可以有多个线程占有
- 写锁
- 也叫 独占锁;一次只能由一个线程占有

9.2、用法
类似于 Lock 锁
private ReadWriteLock lock = new ReentrantReadWriteLock();//定义读写锁
lock.readLock().lock();//添加读锁
try {
//业务代码
} finally {
lock.readLock().unlock();//释放读锁
}
lock.writeLock().lock();//添加写锁
try {
//业务代码
} finally {
lock.writeLock().unlock();//释放写锁
}
9.3、示例
package readWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author ajun
* Date 2021/7/5
* @version 1.0
* 读写锁
*/
public class Demo01 {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();//缓存类
//循环写
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.push("k-"+temp,"v-"+temp);
},String.valueOf(i)).start();
}
//循环读
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.get("k-"+temp);
},String.valueOf(i)).start();
}
}
}
//自定义加锁缓存类
class MyCacheLock {
private volatile Map<String, String> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();//读写锁
//读
public String get(String key) {
lock.readLock().lock();
String s="";
try {
System.out.println(Thread.currentThread().getName() + " 读取 " + key);
s = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读取成功");
} finally {
lock.readLock().unlock();
}
return s;
}
//写
public void push(String key,String value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 写入 " + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + " 写入成功");
} finally {
lock.writeLock().unlock();
}
}
}

10、阻塞队列
10.1、简介
写入:如果队列满了,就阻塞,等待消费
读取:如果队列空了,就阻塞,等待生产

10.2、队列关系图

10.3、使用场景
多线程并发处理
线程池
10.4、四组API
ArrayBlockingQueue
LinkedBlockingQueue
| 方式 | 抛出异常 | 不会抛出异常 | 阻塞等待 | 超时不等待 |
|---|---|---|---|---|
| 添加操作 | add() | offer() 供应 | put() | offer(obj,int,timeunit.status) |
| 移除/获取 | remove() 移除 | poll() 获得 | take() | poll(int,timeunit.status) |
| 判断队列首部 | element() | peek() 偷看,偷窥 |
remove() :移除,返回元素
public E remove() {}remove(Object o) :移除,返回boolean
public boolean remove(Object o) {}offer(obj,int,timeunit.status) :添加、供应
- obj :需要添加的元素
- int :等待的时间。如果超出时间添加不成功就退出
- timeunit.status : 时间单位。如秒
poll(int,timeunit.status) :获取
- int :等待的时间。如果超出时间获取不成功就退出
- timeunit.status : 时间单位。如秒
1、第一组
add()、remove()、element()
操作不成功时会抛出异常
package blockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author ajun
* Date 2021/7/6
* @version 1.0
* 阻塞队列四组API
*/
public class demo01 {
public static void main(String[] args) {
api1();
}
/*
add()、remove()、element()
操作不成功时会抛出异常
*/
public static void api1() {
//队列大小
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
//添加
System.out.println(queue.add("a"));
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
//超出队列大小,第四个元素添加不成功,抛出异常
//java.lang.IllegalStateException
//System.out.println(queue.add("d"));
System.out.println("-------------");
System.out.println("队首" + queue.element());
System.out.println(queue.remove());
System.out.println("队首" + queue.element());
System.out.println(queue.remove());
System.out.println("队首" + queue.element());
System.out.println(queue.remove());
//超出队列大小,第四个元素清除不成功,抛出异常
//java.util.NoSuchElementException
//System.out.println(queue.remove());
}
}

2、第二组
offer()、poll()、peek()
操作不成功时不抛出异常,有返回值
/*
offer()、poll()、peek()
操作不成功时不抛出异常,有返回值
*/
public static void api2() {
//队列大小
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
//添加
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
//超出队列大小,添加不成功,返回false,不抛异常
System.out.println(queue.offer("d"));
System.out.println("-------------");
System.out.println("队首" + queue.peek());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println("队首" + queue.peek());
System.out.println(queue.poll());
//超出队列大小,获取不成功,返回null,不抛异常
System.out.println("队首" + queue.peek());
System.out.println(queue.poll());
}

3、第三组
put()、take()
操作不成功时阻塞等待
/*
put()、take()
操作不成功时阻塞等待
*/
public static void api3() throws InterruptedException {
//队列大小
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
//添加
queue.put("a");//无返回值,不能打印输出
queue.put("b");
queue.put("c");
//queue.put("d"); //队列没有位置就会阻塞
System.out.println(queue.take());
System.out.println("队首" + queue.peek());
System.out.println(queue.take());
System.out.println("队首" + queue.element());
System.out.println(queue.take());
//System.out.println(queue.take());//超出队列中现在元素数量,阻塞等待
}



4、第四组
offer(obj,int,timeunit.status)、poll(int,timeunit.status)
操作不成功时,阻塞等待,等待超时就退出;操作成功时,不需等待,直接退出;
/*
offer(obj,int,timeunit.status)、poll(int,timeunit.status)
操作不成功时超时等待
*/
public static void api4() throws InterruptedException {
//队列大小
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
//添加
System.out.println(queue.offer("a", 2, TimeUnit.SECONDS));
System.out.println(queue.offer("b", 2, TimeUnit.SECONDS));
System.out.println(queue.offer("c", 2, TimeUnit.SECONDS));
//超出队列大小,添加不成功。等待2秒后退出,返回false,不阻塞
System.out.println(queue.offer("d", 2, TimeUnit.SECONDS));
System.out.println("---------------");
System.out.println(queue.poll(1, TimeUnit.SECONDS));
System.out.println(queue.poll(1, TimeUnit.SECONDS));
System.out.println("队首" + queue.peek());
System.out.println(queue.poll(1, TimeUnit.SECONDS));
//获取数量超出队列中现有数量,获取不成功。等待1秒后退出,返回null,不阻塞
System.out.println(queue.poll(1, TimeUnit.SECONDS));
}

10.5、同步队列
SynchronousQueue
最多只存一个元素
进去一个元素,必须等待取出来之后,才能再往里面放一个元素
存:put()、取:take();用其它存取方法,达不到同步存取目的
package blockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @author ajun
* Date 2021/7/6
* @version 1.0
* 同步队列
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> queus = new SynchronousQueue<>();//同步队列
//存入
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 存入:1");
queus.put("1");
System.out.println(Thread.currentThread().getName() + " 存入:2");
queus.put("2");
System.out.println(Thread.currentThread().getName() + " 存入:3");
queus.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
//queus.offer();
//queus.add();
//queus.offer(,2, TimeUnit.SECONDS);
},"存入线程").start();
//取出
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
//queus.remove();
//queus.poll();
//queus.poll(2,TimeUnit.SECONDS);
},"取出线程").start();
}
}


11、线程池(重点)
三大方法、七大参数、4种拒绝策略
一、池化技术
-
背景:程序运行: 占用系统的资源 ! 优化CPU资源的使用 ===>池化技术
- 线程池,连接池,内存池,对象池 …
-
原理:提前准备好一些资源,如果要用,就来拿;用完之后返还
-
好处:
- 降低资源消耗
- 提高响应速度
- 方便管理
线程复用,可以控制最大并发数,管理线程
二、三大方法
Executors 工具类创建线程池有三种方法
阿里巴巴开发手册禁止用 Executors 创建线程池,而用 ThreadPoolExecutor 的方式

1、单一线程
Executors.newSingleThreadExecutor();//单个线程
2、固定数量线程
Executors.newFixedThreadPool(5);//固定数量线程
3、弹性数量线程
Executors.newCachedThreadPool();//弹性数量线程
package threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author ajun
* Date 2021/7/6
* @version 1.0
* 用 Executors 工具类的三种方法创建线程
*/
public class ExecutorsDemo {
public static void main(String[] args) {
//ExecutorService service = Executors.newSingleThreadExecutor();//单个线程
ExecutorService service = Executors.newFixedThreadPool(5);//固定数量线程
//ExecutorService service = Executors.newCachedThreadPool();//弹性数量线程
try {
for (int i = 1; i <= 10; i++) {
service.submit(() -> {
System.out.println(Thread.currentThread().getName() + " ok!");
});
}
} finally {
service.shutdown();//关闭线程池
}
}
}

三、七大参数
ThreadPoolExecutor
1、三大方法底层分析
用 Executors 创建线程池的三种方法,底层都是调用 ThreadPoolExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

2、七大参数
ThreadPoolExecutor 构造器
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大的线程池大小
long keepAliveTime, //存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory,//线程工厂,创建线程的,一般不动
RejectedExecutionHandler handler) {//拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

3、示例
银行大厅接客流程
核心窗口数量:corePoolSize
最大窗口数量:maximumPoolSize (核心窗口数量 + 备用窗口数量)
窗口空闲时间:keepAliveTime、TimeUnit unit ;达到空闲时间时,窗口关闭
候客区容量:BlockingQueue
workQueue 的容量 大厅拒绝接客:当所有窗口都在接客,并且候客区也已满员,大厅不再接客
最大接待数量:最大窗口数量 + 候客区容量
- 核心窗口接客、队列未满
- 核心窗口接客、队列已满、陆续开启备用窗口
- 所有窗口接客、队列未满、大厅仍可以接客
- 所有窗口接客、队列已满、大厅拒绝接客
- 所有窗口接客、队列未满、出现空闲窗口,窗口空闲到一定时间开始关闭

四、四种拒绝策略
/**
* new ThreadPoolExecutor.AbortPolicy() 超出最大处理线程抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() 从哪个线程创建就由那个线程执行
* new ThreadPoolExecutor.DiscardPolicy() 队列满了不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() 尝试去和第一个竞争,也不会抛出异常
*/

policy [ˈpɑːləsi]
n. 政策、策略
五、示例
package threadPool;
import java.util.concurrent.*;
/**
* @author ajun
* Date 2021/7/6
* @version 1.0
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//线程池
ExecutorService service = new ThreadPoolExecutor(
3,//核心线程数
Runtime.getRuntime().availableProcessors(),//最大线程数 等于 CPU核数
2,//线程空闲时间
TimeUnit.SECONDS,//时间单位
new LinkedBlockingQueue<>(3),//阻塞队列
Executors.defaultThreadFactory(),//线程工厂
//new ThreadPoolExecutor.AbortPolicy()//拒绝策略
//new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
);
//调用线程
try {
for (int i = 1; i <= 30; i++) {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
final int temp = i;
service.submit(() -> {
System.out.println(Thread.currentThread().getName() + " : " + temp + " -> ok");
});
}
} finally {
service.shutdown();
}
}
}
六、小结
线程池的最大线程数量到底应该如何定义?
-
CPU密集型
- CPU几核最大线程数就是几;可以保证CPU的效率最高
Runtime.getRuntime().availableProcessors() //CPU核数
-
IO 密集型
- 判断程序中十分耗I/O的线程,大于等于两倍
本文来自博客园,作者:土味儿,转载请注明原文链接:https://www.cnblogs.com/tuwer/articles/15065173.html
浙公网安备 33010602011771号