JUC
视频参考:https://www.bilibili.com/video/BV1Kw411Z7dF?p=43
文档参考:https://blog.csdn.net/weixin_47872288/article/details/119453092
1.锁的实现方法
1.1synchronized
public class hello {
static class Ticket {
private int total=30;
public synchronized void sale()
{
if(total>0)
System.out.println(Thread.currentThread().getName()+" :"+"卖出: "+(total--)+ "剩下:"+total);
}
}
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"AA"
).start();
//
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"BB"
).start();
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"CC"
).start();
}
}
1.2 Lock
import java.util.concurrent.locks.ReentrantLock;
class Ticket {
private int total=30;
private final ReentrantLock lock=new ReentrantLock();
public void sale()
{ lock.lock();
try{
if(total>0)
System.out.println(Thread.currentThread().getName()+" :"+"卖出: "+(total--)+ "剩下:"+total);
}
finally {
lock.unlock();
}
}
}
public class Locktest {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"AA"
).start();
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"BB"
).start();
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.sale();
}
}
},"CC"
).start();
}
}
1.3虚拟唤醒
//第一步 创建资源类,定义属性和操作方法
class Share {
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
if(number != 0) { //判断number值是否是0,如果不是0,等待
this.wait(); //在哪里睡,就在哪里醒
}
//如果number值是0,就+1操作
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void decr() throws InterruptedException {
//判断
if(number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知其他线程
this.notifyAll();
}
}
public class ThreadDemo1 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
//创建线程
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
主要是虚拟唤醒导致:如果一个线程执行完毕后,通知其他线程,该线程又进入等待睡眠,可能会因为某些原因被唤醒后,if结构的语句就不会判断了,一直往下执行,所以需要将if换成while结构,每次都判断。因为wait在哪里睡眠就在哪里被唤醒,结果被某个异常唤醒了后回不去了,if结构不会在判断了,需要更改为while。
1.4 线程间定制化通信
AA打印5此,BB打印10次,CC打印15次,一共进行10轮
private Lock lock = new ReentrantLock();
分别创建三个开锁通知private Condition c1 = lock.newCondition();
具体资源类中的A线程代码操作
上锁,(执行具体操作(判断、操作、通知),解锁)放于try、finally
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//第一步 创建资源类
class ShareResource {
//定义标志位
private int flag = 1; // 1 AA 2 BB 3 CC
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while(flag != 1) {
//等待
c1.await();
}
//干活
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//通知
flag = 2; //修改标志位 2
c2.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 2) {
c2.await();
}
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 3;
//通知CC线程
c3.signal();
}finally {
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 3) {
c3.await();
}
for (int i = 1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 1;
//通知AA线程
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
2.集合的线程安全
2.1.集合不安全情况
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
//之所以线程不安全,是因为
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for(int i=0;i<10;i++)
{
new Thread(()->{
//之所以线程不安全,是因为ArrayList的add方法里面没有锁
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},Integer.toString(i)).start();
}
}
}
2.2.线程不安全问题解决方法
1.vector:线程安全类,添加方法是加锁的
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list=new Vector<>();
for(int i=0;i<10;i++)
{
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},Integer.toString(i)).start();
}
}
}
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
2.Collections
public static void main(String[] args) {
//List<String> list=new Vector<>();
List<String> list= Collections.synchronizedList(new ArrayList<>());
for(int i=0;i<10;i++)
{
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},Integer.toString(i)).start();
}
}
原理分析:synchronizedList方法判定List集合 是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList. SynchronizedList其中的add方法加锁的
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
3.CopyOnWriteArrayList
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for(int i=0;i<10;i++)
{
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},Integer.toString(i)).start();
}
}
}
读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}CopyOnWriteArrayList 写入操作的实现
CopyOnWriteArrayList 写入操作 add()
方法在添加集合的时候加了锁,保证同步,避免多线程写的时候会 copy 出多个副本。
CopyOnWriteArraySet解决HashSet不安全问题
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 加锁
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // 拷贝新数组
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock(); // 释放锁
}
}
3多线程锁
1.synchronized锁
某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法(普通方法无所谓)
所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象
synchronized锁的是方法,则是对象锁
同个对象锁的机制要等待,不同对象锁的机制调用同一个不用等待
加了static则为class锁而不是对象锁
import java.util.concurrent.TimeUnit;
class Phone {
public 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");
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
new Thread(() -> {
try {
//phone.sendEmail();
phone.getHello();
//phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
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
1.- 同样的对象访问不同的同步锁,是按照顺序执行
同样的对象访问同步锁与不同步锁,是先不同步锁执行
不同对象访问不同同步锁,按照顺序执行
2.- 同一对象访问不同静态同步锁,按照顺序执行
不同对象访问不同静态同步锁,按照顺序执行
3.- 同一对象访问一个静态同步锁,一个同步锁,先执行同步锁
不同对象访问一个静态同步锁,一个同步锁,先执行同步锁
即先出同步锁在出静态同步锁
2.公平锁非公平锁
- 公平锁:效率相对低
- 非公平锁:效率高,但是线程容易饿死
非公平锁:
都是A线程执行,而BC线程都没执行到,出现了非公平锁
公平锁:
private final ReentrantLock lock=new ReentrantLock(true);
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
构造方法
private int total=30;
private final ReentrantLock lock=new ReentrantLock(true);
public void sale()
{ lock.lock();
try{
if(total>0)
System.out.println(Thread.currentThread().getName()+" :"+"卖出: "+(total--)+ "剩下:"+total);
}
finally {
lock.unlock();
}
}
3.可重入锁
synchronized和lock都是可重入锁
- sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁
- 可重入锁也叫递归锁
可重入锁之后,破解第一把之后就可以一直进入到内层结构
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncLockDemo {
public synchronized void add() {
add();
}
public static void main(String[] args) {
//Lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 外层");
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 内层");
}finally {
//释放锁
lock.unlock();
}
}finally {
//释放做
lock.unlock();
}
},"t1").start();
//创建新线程
new Thread(()->{
lock.lock();
System.out.println("aaaa");
lock.unlock();
},"aa").start();
}
}
4.死锁
验证是否是死锁
- jps 类似于linux中的
ps -ef
查看进程号 - jstack 自带的堆栈跟踪工具
4.辅助类
4.1.CountDownLatch
该类的构造方法为
CountDownLatch(int count)构造一个用给定计数初始化的CountDownLatch在这里插入代码片
两个常用的主要方法
await() 使当前线程在锁存器倒计数至零之前一直在等待,除非线程被中断
countDown()递减锁存器的计数,如果计数达到零,将释放所有等待的线程
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句
具体步骤可以演化为定义一个类,减1操作,并等待到0,为0执行结果
通过具体的案例进行加深代码
6个同学陆续离开教室之后,班长才能锁门
如果不加 CountDownLatch类,会出现线程混乱执行,同学还未离开教室班长就已经锁门了
4.2CyclicBarrier
该类是一个同步辅助类,允许一组线程互相等到,直到到达某个公共屏障点,在设计一组固定大小的线程的程序中,这些线程必须互相等待,这个类很有用,因为barrier在释放等待线程后可以重用,所以称为循环barrier
常用的构造方法有:
CyclicBarrier(int parties,Runnable barrierAction)创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作
常用的方法有:
await()在所有的参与者都已经在此barrier上调用await方法之前一直等待
import java.util.concurrent.CyclicBarrier;
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER,()->{
System.out.println("*****集齐7颗龙珠就可以召唤神龙");
});
//集齐七颗龙珠过程
for (int i = 1; i <=7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
总结:
CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作
4.3Semaphore
一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动
具体常用的构造方法有:
Semaphore(int permits)创建具有给定的许可数和非公平的公平设置的Semapore
具体常用的方法有:
acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
release()释放一个许可,将其返回给信号量
设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,release在finally中执行
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
//6辆汽车,停3个车位
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+" ------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jb75CdM7-1635819269064)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635774915283.png)]
5.读写锁
悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低
乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号
表锁:整个表操作,不会发生死锁
行锁:每个表中的单独一行进行加锁,会发生死锁
读锁:共享锁(可以有多个人读),会发生死锁
写锁:独占锁(只能有一个人写),会发生死锁
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//资源类
class MyCache {
//创建map集合
private volatile Map<String,Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//放数据
public void put(String key,Object value) {
//添加写锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
rwLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//添加读锁
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放读锁
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
TimeUnit.MICROSECONDS.sleep(300);
//创建线程取数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
无锁:多线程抢夺资源
synchronized和ReentrantLock,都是独占,每次只可以一个操作,不能共享
ReentrantReadWriteLock,读读可以共享,提升性能,但是不能多人写。缺点:造成死锁(一直读,不能写),读进程不能写,写进程可以读。
写锁降级为读锁(一般等级写锁高于读锁)
import java.util.concurrent.locks.ReentrantReadWriteLock;
//演示读写锁降级
public class Demo2 {
public static void main(String[] args) {
//可重入读写锁对象
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
//1 获取写锁
writeLock.lock();
System.out.println("manongyanjiuseng");
//2 获取读锁
readLock.lock();
System.out.println("---read");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
}
}
6.阻塞队列
当队列是空的,从队列中获取元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfgAXNDK-1635819269069)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635815447771.png)]
1.ArrayBlockingQueue
基于数组的阻塞队列
由数组结构组成的有界阻塞队列
-ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,无法并行
- LinkedBlockingQueue
基于链表的阻塞队列
由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能
3.DelayQueue
使用优先级队列实现的延迟无界阻塞队列
DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞
4.PriorityBlockingQueue
基于优先级的阻塞队列
支持优先级排序的无界阻塞队列
不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者
5.SynchronousQueue
一种无缓冲的等待队列
相对于有缓冲的 BlockingQueue 来说,少了一个中间经销商的环节(缓冲区)
不存储元素的阻塞队列,也即单个元素的队列
声明一个 SynchronousQueue 有两种不同的方式,它们之间有着不太一样的行为。
公平模式和非公平模式的区别:
• 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞
多余的生产者和消费者,从而体系整体的公平策略;
• 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者
而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理
6.LinkedTransferQueue
由链表结构组成的无界阻塞 TransferQueue 队列
由链表组成的无界阻塞队列
预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,生成一个节点(节点元素为 null)入队,消费者线程被等待在这个节点上,生产者线程入队时发现有一个元素为 null 的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回
7.LinkedBlockingDeque
由链表结构组成的双向阻塞队列
阻塞有两种情况
插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时再该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException异常
读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可以通过设置超时参数
7.线程池
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度
特点:
降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executors.newFixedThreadPool(int)一池N线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
Executors.newSingleThreadExecutor()一池一线程
**ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); **//一个窗口
Executors.newCachedThreadPool()一池可扩容根据需求创建线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
执行线程execute()
关闭线程shutdown()
//演示线程池三种常用分类
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池五线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
//一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
//一池可扩容线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool3.shutdown();
}
}
}
一池多线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJw1aeZq-1635819269071)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635817070220.png)]
一池一线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxxN0ONe-1635819269072)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635817102454.png)]
一池可扩容线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhWVJxZt-1635819269073)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635817138349.png)]
三个线程都有如下代码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
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;
}
int corePoolSize, 常驻线程数量(核心)
int maximumPoolSize,最大线程数量
long keepAliveTime,TimeUnit unit,线程存活时间
BlockingQueue workQueue,阻塞队列(排队的线程放入)
ThreadFactory threadFactory,线程工厂,用于创建线程
RejectedExecutionHandler handler拒绝策略(线程满了)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtSoqp6B-1635819269074)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635817632526.png)]
- 在执行创建对象的时候不会创建线程
- 创建线程的时候execute()才会创建
- 先到常驻线程,满了之后再到阻塞队列进行等待,阻塞队列满了之后,在往外扩容线程,扩容线程不能大于最大线程数。大于最大线程数和阻塞队列之和后,会执行拒绝策略。
阻塞队列为3,常驻线程数2,最大线程数5
具体的拒绝策略有:
-
抛异常
-
谁调用找谁
-
抛弃最久执行当前
-
不理不问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kN44ku4G-1635819269075)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\1635817753770.png)]
自定义线程池
import java.util.concurrent.*;
//自定义线程池创建
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}