JUC
1. 概述
线程和进程的区别
进程是程序运行的基本单位,一个程序运行就是一个进程。
线程是cpu调度的基本单位,共享内存
1.1 JUC
juc
是java.util.courrent 工具包的简称,jdk1.5之后出现的
1.2 线程状态
新建,就绪,运行,阻塞,死亡
1.3 wait 和sleep的区别
sleep 需要指定睡眠时间,wait不需要
sleep是Thread类的方法,wait是object方法,必须在同步代码块中使用(synchronized)
sleep不会释放对象的锁,wait会释放对象的锁
1.4 守护线程和非守护线程
守护线程就是一种后台进程,当所以的用户线程结束后才会停止
非守护线程:普通的线程
2. Lock 接口
2.1 Synchronized
修饰同步代码块或者同步方法
synchronized定义方法,不能被继承
方法: 普通方法锁的是this, 静态方法锁定是Class类
代码块:锁定的是监视器对象 synchronized(this)
class Ticket {
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale() {
//判断:是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" :"+(number--)+" "+number);
}
}
}
2.2 Lock
Lock和Synchronized的区别:
Synchronized不需要手动释放锁,Lock需要手动释放锁
Synchronized是JVM层面,Lock属于API层面
Synchronized是非公平锁,Lock可以是公平锁和非公平锁
Synchronized不能被中断,Lock可以被中断和精准唤醒
2.3 Lock接口
public interface Lock
{ void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
1. lock
获取锁
如果锁被其他线程获取,进入等待
需要手动释放锁
Lock lock = new ReetrantLock();
lock.lock();
try {
}catch(Exeception e) {
}finally {
lock.unlock();
}
2.newCondition
Synchronized的wait()和notify()来实现等待和通知
Lock通过newCondition()返回Condition对象来实现等待通知
await() == wait()
sigal() == notify()
2.4 ReentrantLock
可重入锁
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Airmal{
private int number;
public Lock lock=new ReentrantLock();
public Condition condition=lock.newCondition();
public void incrnumber(){
lock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void decrenumber(){
lock.lock();
try {
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
}
public class ProdeCustomerLockDemo {
public static void main(String[]ags){
Airmal airmal=new Airmal();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.incrnumber();
}
},"A").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.decrenumber();
}
},"B").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.incrnumber();
}
},"C").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.decrenumber();
}
},"D").start();
}
}
精准唤醒
A B C 三个线程启动,要A-B-C线程依次执行,A打印5次.B打印10,C打印15次
思路:一把锁配多分钥匙,通过标志位来判断
//Condition 精确控制
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A B C 三个线程
* A -B-C 依次执行
* A 打印5 次
* B 打印 10
* C 打印 15 次
*/
class Airmals{
private int number=1; //标志位 1:A B:2 C:3
private Lock lock=new ReentrantLock();
Condition c1=lock.newCondition();
Condition c2=lock.newCondition();
Condition c3=lock.newCondition();
public void pring5(){
lock.lock();
try {
while (number!=1){
c1.await();
}
for (int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=2;
c2.signal(); //唤醒B
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void pring10(){
lock.lock();
try {
while (number!=2){
c2.await();
}
for (int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=3;
c3.signal();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void pring15(){
lock.lock();
try {
while (number!=3){
c3.await();
}
for (int i=1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=1;
c1.signal();
}catch (Exception e){
}finally {
lock.unlock();
}
}
}
public class ConditionDemo {
public static void main(String[] args) {
Airmals airmals=new Airmals();
new Thread(()->{
for (int i=1;i<=10;i++){
airmals.pring5();
}
},"A").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmals.pring10();
}
},"B").start();
new Thread(()->{
for (int i=1;i<=15;i++){
airmals.pring15();
}
},"C").start();
}
}
2.5 ReadWirteLock
这是一个接口,实现类ReentrantReadWriteLock
ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个 方法:readLock()和 writeLock()用来获取读锁和写锁。
为解决Lock不管读写都锁的效率低,ReadWriteLock读不上锁,写上锁
//多个线程操作一个资源
//读-读可以存在
//读-写不能并存
//写-写不能并存
class MyLock{
private Map<String,Object> mylock=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
public void put(String key,Object value) throws InterruptedException {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"---写入数据");
TimeUnit.MICROSECONDS.sleep(300);
System.out.println(Thread.currentThread().getName()+"\t"+"---写入数据成功");
}catch (Exception e){
}finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) throws InterruptedException {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"----读取数据");
TimeUnit.MICROSECONDS.sleep(300);
System.out.println(Thread.currentThread().getName()+"\t"+"---读取数据成功");
}catch (Exception e){
}finally {
readWriteLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyLock myLock=new MyLock();
for (int i=1;i<=3;i++){
final String temp= valueOf(i);
//写入操作
new Thread(()->{
try {
myLock.put(temp, new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, valueOf(i)).start();
}
for (int i=1;i<=3;i++){
final String temp= valueOf(i);
new Thread(()->{
try {
myLock.get(temp);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, valueOf(i)).start();
}
}
}
1 —写入数据
1 —写入数据成功
2 —写入数据
2 —写入数据成功
3 —写入数据
3 —写入数据成功
1 ----读取数据
2 ----读取数据
3 ----读取数据
1 —读取数据成功
2 —读取数据成功
3 —读取数据成功
3.线程之间通信
通信模型:共享内存和消息传递
3.1 synchronized方案
高内聚低耦合
/**
* 生成者消费者demo
* 线程编程:
* 1.高内聚低耦合,线程操作资源
* 2.判断/干活/通知
* 3.防止虚假唤醒
*/
class Airmant{
private int number=0;
public synchronized void increnumber() throws InterruptedException {
//判断
while (number!=0){
this.wait();
}
//干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
this.notifyAll();
}
public synchronized void decrennumber() throws InterruptedException {
//判断
while (number==0){
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
this.notifyAll();
}
}
public class ProdeCustomerDemo {
public static void main(String[]ags){
Airmant airmant=new Airmant(); //高内聚
new Thread(()->{
try {
for(int i=1;i<=10;i++){
airmant.increnumber();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for(int i=1;i<=10;i++){
airmant.decrennumber();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
3.2 Lock方案
class Airmal{
private int number;
public Lock lock=new ReentrantLock();
public Condition condition=lock.newCondition();
public void incrnumber(){
lock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void decrenumber(){
lock.lock();
try {
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
}
public class ProdeCustomerLockDemo {
public static void main(String[]ags){
Airmal airmal=new Airmal();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.incrnumber();
}
},"A").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.decrenumber();
}
},"B").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.incrnumber();
}
},"C").start();
new Thread(()->{
for (int i=1;i<=10;i++){
airmal.decrenumber();
}
},"D").start();
}
}
4.集合线程
java.util.ConcurrentModificationExceptio
public class NotSafeDemo {
/**
* 多个线程同时对集合进行修改
* @param args
*/
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 100; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString());
list.forEach(System.out::println);
}, "线程" + i).start();
}
}
}
4.1 List
底层:new ArrayList本质上是new 一个Object类型的数组
默认大小为10,在jdk1.7会初始化为10,jdk1.8采用懒加载在add的时候才会初始化
默认扩容为原来的1.5倍
底层的复制采用的是Arrays.copyof() - > System.copyof()
解决方案
Vector
public class nosaleCollection {
public static void main(String[]ags){
/* List <String>list=new ArrayList();*/
List <String>list=new Vector<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Collections.synchronized
public class nosaleCollection {
public static void main(String[]ags){
List<String>list= Collections.synchronizedList(new ArrayList<>());
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList
写时复制
采用读写分离的思想,保证最终一致性,写入数据的时候创建一个新的容器,将原来的数据复制,添加元素到新的容器中,原来的引用指向新的容器
优点: 读写分离,并发度高
缺点: 需要创建新的容器,可能频繁GC
写的时候读取数据不一致,不能保证强一致性,保证最终一致性
public class nosaleCollection {
public static void main(String[]ags){
List<String> list=new CopyOnWriteArrayList<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
4.2 Map
默认大小是16,扩容为原来的1倍,如16变成32
解决
Collections.synchronizedMap
ConcurrentHashMap
HashTable
4.3 Set
HashSet底层采用的是HashMap
HashSet的add()方法加只有一个参数,hashMap(key,value)原因是haspMap(e,object)
解决方案
Collections.synchronizedSet
public class nosaleCollection {
public static void main(String[]ags){
Set <String>set=Collections.synchronizedSet(new HashSet<>());
for (int i=1;i<=30;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
},String.valueOf(i)).start();
}
}
private static void nosaftList() {
List<String> list=new CopyOnWriteArrayList<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteHashSet
public class nosaleCollection {
public static void main(String[]ags){
Set <String>set=new CopyOnWriteArraySet<>();
for (int i=1;i<=30;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
private static void nosaftList() {
List<String> list=new CopyOnWriteArrayList<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
5.多线程问题
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留 4 秒TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println(" ---- sendEmail");
}
public void getHello() {
System.out.println(" --- getHello");
}
}
1 标准访问,先打印短信还是邮件 ------sendSMS ------sendEmail
2 停 4 秒在短信方法内,先打印短信还是邮件 ------sendSMS ------sendEmail
3 新增普通的hello 方法,是先打短信还是 hello ------getHello ------sendSMS
4 现在有两部手机,先打印短信还是邮件 ------sendEmail ------sendSMS
5 两个静态同步方法,1 部手机,先打印短信还是邮件 ------sendSMS ------sendEmail
6 两个静态同步方法,2 部手机,先打印短信还是邮件 ------sendSMS ------sendEmail
7 1 个静态同步方法,1 个普通同步方法,1 部手机,先打印短信还是邮件 ------sendEmail ------sendSMS
8 1 个静态同步方法,1 个普通同步方法,2 部手机,先打印短信还是邮件 ------sendEmail ------sendSMS
具体表现为以下 3 种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class 对象。
对于同步方法块,锁是Synchonized 括号里配置的对象
6. Callable
6.1 实现线程的方式
-
继承Thread
-
实现Runnable接口
-
实现Callable接口
-
线程池创建
6.2 Callable接口
//有返回值的线程接口 callable
class MyThread implements Callable<Integer>
{
@Override
public Integer call() throws Exception {
System.out.println("======Callable==========");
return 1024;
}
}
public class CallbackDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask,"a").start();
Integer task = (Integer) futureTask.get();
System.out.println(task);
}
}
6.3 Future接口
public boolean cancel(boolean mayInterrupt):用于停止任务
如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务
public Object get()抛出 InterruptedException,ExecutionException:用于获取任务的结果。 如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果
• public boolean isDone():如果任务完成,则返回 true,否则返回 false
6.4 FutureTask
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些 作业交给 Future 对象在后台完成
- 主线程需要的时候通过Future对计算结果
- 多用于耗时的计算
7.JUC三大辅助类
7.1 CountDownLatch
减少计数
CountDownLatch 类设置一个计数器
通过countDown方法减少1,使用await等待计数器不大于0,让后执行await之后的语句
countdownlatch 调用await方法,这个线程会阻塞
countDown方法计数器减1
当计数器为0,await阻塞的线程会被唤醒
//6个人全部离开,主main才关门
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i=1;i<=6;i++)
{
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("main 关门");
}
}
7.2 CyclicBarrier
循环栅栏
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中 CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一 次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后 的语句。可以将 CyclicBarrier 理解为加 1 操作
public class CyclicBrrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("*****召唤神龙");
});
for (int i=1;i<=7;i++){
final Integer temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 收集了"+temp+"龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
7.3 Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线 程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证,release 方法释放许可
场景: 抢车位, 6 部汽车 3 个停车位
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3); //模拟资源类,有3个空车位
for (int i=1;i<=6;i++){
new Thread(()->{
try {
semaphore.acquire(); //数字-1
System.out.println(Thread.currentThread().getName()+"\t抢占了车位");
TimeUnit.SECONDS.sleep(4);
System.out.println(Thread.currentThread().getName()+"\t离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release(); //数字+1
}
},String.valueOf(i)).start();
}
}
}
8.读写锁
对共享资源进行读写的时候,写操作不是那么频繁。
读情况下不需要加锁
ReentrantReadWriteLock 它表示两个锁,一个是读操作为共享锁;一个写相关的锁,为排它锁
读锁只能读锁
写锁其他不能加读锁和写锁
//多个线程操作一个资源
//读-读可以存在
//读-写不能并存
//写-写不能并存
class MyLock{
private Map<String,Object> mylock=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
public void put(String key,Object value) throws InterruptedException {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"---写入数据");
TimeUnit.MICROSECONDS.sleep(300);
System.out.println(Thread.currentThread().getName()+"\t"+"---写入数据成功");
}catch (Exception e){
}finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) throws InterruptedException {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"----读取数据");
TimeUnit.MICROSECONDS.sleep(300);
System.out.println(Thread.currentThread().getName()+"\t"+"---读取数据成功");
}catch (Exception e){
}finally {
readWriteLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyLock myLock=new MyLock();
for (int i=1;i<=3;i++){
final String temp= valueOf(i);
//写入操作
new Thread(()->{
try {
myLock.put(temp, new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, valueOf(i)).start();
}
for (int i=1;i<=3;i++){
final String temp= valueOf(i);
new Thread(()->{
try {
myLock.get(temp);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, valueOf(i)).start();
}
}
}
9.阻塞队列
9.1 BlockingQueue
队列FIFO
阻塞队列具有阻塞功能,没有元素的时候获取元素会阻塞线程释放CPU
队列满的时候增加元素的时候会阻塞
会自动唤醒线程
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
抛出异常 | 当阻塞队列满时,在add会抛出异常 当阻塞队列为空,remove会抛出异常 |
---|---|
特殊值 | 插入方法,成功true 否则为false 移除方法,成功队列元素,没有就为null |
一直阻塞 | 队满加入,阻塞或者响应中断 队列为空 ,take会一直阻塞消费者线程 |
超时退出 | 队满,队列阻塞生成者一段时间。 |
/**
* 阻塞队列
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// List list = new ArrayList();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
// System.out.println(blockingQueue.add("a"));
// System.out.println(blockingQueue.add("b"));
// System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("x"));
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// 第二组
// System.out.println(blockingQueue.offer("a"));
// System.out.println(blockingQueue.offer("b"));
// System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("x"));
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// 第三组
// blockingQueue.put("a");
// blockingQueue.put("b");
// blockingQueue.put("c");
// //blockingQueue.put("x");
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// 第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("a",3L, TimeUnit.SECONDS));
}
}
9.2 种类
1.ArrayBlockingQueue
数组组成的有界阻塞队列
维护了一个定长数组
保存两个整型变量,分别标识队列的头部和尾部在数组中的位置
生产者放入数据和消费者消费数据,公用一个
同一个对象锁
,LinkedBlockingQueue使用的不是同一个对象锁与LinkedBlockingQueue不同的还有插入删除会产生一个额外的Node对象,ArrayBlockingQueue不会产生
2. LinkedBlockingQueue
链表组成的有阻塞队列
大小为Integer.MAX_VALUE
该队列由一个链表构成
LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生 产者端和消费者端分别采用了
独立的锁
来控制数据同步
3.PriorityBlockingQueue
优先级排序的无界阻塞队列
优先级的判断是通过Compator对象决定
不会阻塞生产者,只会没有消费者可消费的数据时,才会阻塞消费者
生产者生产数据的速度绝对不能快于消费者消费 数据的速度
4.SynchronousQueue
不存储元素的阻塞队列,也即单个元素的队列
5. DelayQueue
使用优先级队列实现的延迟无界阻塞队列
DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到 该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的 操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻 塞。
6. LinkedTransferQueue
由链表组成的无界阻塞队列
预占模式
消费者获取元素的时候,如果队列不为空,取元素
队列为空,生成一个节点(元素为null),生成者入队时发现为null节点,生产者不入队,将元素填充。并唤醒该节点等待的线程,被唤醒的线程取元素
7. LinkedBlockingDeque
由链表组成的双向阻塞队列
9.3 小结
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件 满足,被挂起的线程又会自动被唤起
为什么需要 BlockingQueue? 在 concurrent 包发布以前,在多线程环境下, 我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全, 而这会给我们的程序带来不小的复杂度。使用后我们不需要关心什么时候需要 阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手 包办了
10.线程池
存放线程的池子
不需要每次去创建和销毁线程降低资源销毁
提高响应速度
便于线程的管理
10.1 核心参数
- corePoolSize 核心线程数
- maxinumPoolSize 最大线程数
- keepAliveTime 空闲线程存活时间
- unit 时间单位
- workQueue 阻塞队列
- threadFactory 工厂
- handler 拒绝策略
10.2 原理
提交任务,当前线程数 < corepoolsize 创建核心线程处理
currentThreadNum > corePoolSize 但阻塞队列没有满时放入阻塞队列中
阻塞队列满了的时候,线程数<= maxinumpoolsize时 ,创建非核心线程数进行处理
线程数 > maxinumpoolsize 时,采用拒绝策略
10.3 拒绝策略
AbortPolicy: 丢弃任务,线程池默认的拒绝策略
CallerRunsPolicy:回退给调用者
DiscardPolicy :直接丢弃
DiscardOldestPolicy: 触发拒绝策略,丢弃最老的一个
10.4 Executors工具类
1.newCachedThreadPool
作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空 闲线程,若无可回收,则新建线程
- 线程数没有固定,可以达到Integer.MAX_VALUE
- 线程池可以对缓存重复利用和回收
- 当线程池中没有可用线程会重新创建一个线程
/**
* 可缓存线程池
* @return
*/
public static ExecutorService newCachedThreadPool(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较 短,任务多的场景
2.newFixedThreadPool
创建一个可重用固定线程数的线程池
线程池中的线程处于一定的量,可以很好的控制线程的并发量
• 线程可以重复被使用,在显示关闭之前,都将一直存在
• 超出一定量的线程被提交时候需在队列中等待
/**
* 固定长度线程池
* @return
*/
public static ExecutorService newFixedThreadPool(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(10,
10,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严 格限制的场景
3.newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor
特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此 执行
/**
* 单一线程池
* @return
*/
public static ExecutorService newSingleThreadExecutor(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个 线程的场景
public class ThreadPoolDemo1 {
/**
* 火车站 3 个售票口, 10 个用户买票
* @param args
*/
public static void main(String[] args) {
//定时线程次:线程数量为 3---窗口数为 3
ExecutorService threadService = new ThreadPoolExecutor(3,
3,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//10 个人买票
for (int i = 1; i <= 10; i++) {
threadService.execute(()->{
try {
System.out.println(Thread.currentThread().getName() + "窗口,开始卖票");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "窗口买票结束");
}catch (Exception e){
e.printStackTrace();
});
}catch (Exception e){
e.printStackTrace();
}finally {
//完成后结束
}
10.5 线程创建方式
不通过Executors创建线程,因为 FixedThreadPool 和SingleThreadExecutor底层采用的LinkedBlockingQueue最长度为Integer.MAX_VALUE (OOM
), CacheThreadPool的最大线程数为Integer.MAX_VALUE(CPU压力
)
10.6 手动创建
new ThreadPoolExecutor来创建对象
11.Fork /Join
fork 将大任务拆分成小任务
join 将任务进行合并
1.Fork
1.ForkJoinTask
首先需要创建一个 ForkJoin 任务。 该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集 成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
a.RecursiveAction:用于没有返回结果的任务
b.RecursiveTask:用于有返回结果的任务
2.ForkJoinPool
ForkJoinPool :ForkJoinTask 需要通过 ForkJoinPool 来执行
3.RecursiveTask:
继承后可以实现递归(自己调自己)调用的任务
2.实例
/**
* 递归累加
*/
public class TaskExample extends RecursiveTask<Long> {
private int start;
private int end;
private long sum;
/**
* 构造函数
* @param start
* @param end
*/
public TaskExample(int start, int end){
this.start = start;
this.end = end;
}
/**
* The main computation performed by this task.
*
* @return the result of the computation
*/
@Override
protected Long compute() {
System.out.println("任务" + start + "=========" + end + "累加开始");
//大于 100 个数相加切分,小于直接加
if(end - start <= 100){
for (int i = start; i <= end; i++) {
//累加
sum += i;
}
}else {
//切分为 2 块
int middle = start + 100;
//递归调用,切分为 2 个小任务
TaskExample taskExample1 = new TaskExample(start, middle);
TaskExample taskExample2 = new TaskExample(middle + 1, end);
//执行:异步
taskExample1.fork();
taskExample2.fork();
//同步阻塞获取执行结果
sum = taskExample1.join() + taskExample2.join();
}
//加完返回
return sum;
}
}
public class ForkJoinPoolDemo {
/**
* 生成一个计算任务,计算 1+2+3 ........+1000
* @param args
*/
public static void main(String[] args) {
//定义任务
TaskExample taskExample = new TaskExample(1, 1000);
//定义执行对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
//加入任务执行
ForkJoinTask<Long> result = forkJoinPool.submit(taskExample);
//输出结果
try {
System.out.println(result.get());
}catch (Exception e){
e.printStackTrace();
}finally {
forkJoinPool.shutdown();
}
}
}