2.0多线程
文章目录
- 2.0多线程
- (一)线程
- 1.创建
- 2.状态
- 3.守护线程
- (二)并发编程
- 1.JMM模型:可见性,原子性,有序性
- 2.volatile
- 3.锁机制
- 3.1悲观锁:Synchronized、AQS
- 3.1.1Synchronized
- 3.1.2AQS(CAS+CLH)
- (1)AQS简介:
- (2)AQS原理
- ①:原理概述
- ②:资源共享方式
- Ⅰ.独占锁(Exclusive):ReentrantLock
- i.概念:只有一个线程能够执行,又可分为公平锁和非公平锁
- ii.ReentrantLock的公平锁和非公平锁(**默认**):
- iii.ReentrantLock是可重入锁
- iiii.获取锁的四种方法:
- Ⅱ.共享锁(Share):ReentrantReadWriteLock,CountDownLatch,CyclicBarrier,Semaphore
- i.概念:线程可以共享执行
- ii.ReentrantReadWriteLock(读写锁)
- iii.CountDownLatch(倒计时)
- iiii.CyclicBarrier(循环栅栏)
- iiiii.Semaphore(信号量)
- ③:底层模板方法模式自定义同步器
- Ⅰ钩子方法:
- Ⅱ如何自定义同步器:
- 3.2乐观锁:Atomic类(基于CAS理论实现)
- 4.线程池
- 5.ThreadLocal
- (三)实现生产者消费者模式
(一)线程
1.创建
(1)Thread类
/**
* @author zhangshiqin
* @date 2022/2/9 - 10:34
* @description:
*/
public class TestThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" is run");
}
public static void main(String[] args) {
//创建线程4种方式——Thread类
Thread t1 = new TestThread();
t1.start();
}
}
(2)Runnable接口
/**
* @author zhangshiqin
* @date 2022/2/9 - 10:34
* @description:
*/
public class TestThread {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" is run");
}).start();
}
}
(3)Callable接口:get()方法会阻塞直到有结果返回
/**
* @author zhangshiqin
* @date 2022/2/9 - 10:34
* @description:
*/
public class TestThread {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " is run");
return "hello world";
});
new Thread(futureTask).start();
try {
//⭐get()方法在这里会阻塞直到有结果返回
System.out.println(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
(4)线程池
/**
* @author zhangshiqin
* @date 2022/2/9 - 10:34
* @description:
*/
public class TestThread {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " is run");
return "hello world";
});
ThreadPoolExecutor executor = new ThreadPoolExecutor(七大参数);//七大参数
executor.submit(()-> System.out.println("Runnable"));//runnable
executor.submit(futureTask);//callable
}
}
2.状态
新建(new()),就绪(start()),运行(run()),阻塞,挂起,死亡
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2u4QbN6-1686273703941)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220210162833862.png)]
(1)sleep和wait
①sleep(long ms)
Thread类静态方法,让当前线程睡眠进入阻塞状态,时间结束后重新进入就绪态。不会释放锁
需要捕获异常,因为在睡眠中有可能会被可能被其他对象调用它的interrupt(),产生InterruptedException异常
public class TestThread {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(() -> {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is run");
return "hello world";
});
Thread thread = new Thread(futureTask);
thread.start();
}
}
②wait()/wait(long ms)
Object类的成员方法,让当前线程挂起并进入当前(锁对象)相关的等待队列,等待唤醒或超时唤醒。同时导致线程释放锁。
wait()方法、notify()【随机唤醒】方法和notiftAll()方法用于协调多线程对共享数据的存取,所以只能在同步方法或者同步块中使用,否则抛出IllegalMonitorStateException,当被notify唤醒后需要重新抢锁执行后面的代码。可能会被可能被其他对象调用它的interrupt(),产生InterruptedException异常,需要捕获。
如果wait(1000)设置了超时时间,则不需要被notify()/notiftAll()唤醒,超时会自动唤醒,等到抢到锁后接着执行"后面"的代码。否则需要被唤醒不然一直堵塞
public class test{
public static void main(String[] args) {
Integer i = new Integer(1);
new Thread(new waitThread(i)).start();
new Thread(new notifyThread(i)).start();
}
}
class waitThread implements Runnable{
Integer i;
public waitThread(Integer i){
super();
this.i = i;
}
@Override
public void run(){
try{
synchronized(i){
long start = System.currentTimeMillis();
i.wait(1000);
System.out.println("waitThread "+(System.currentTimeMillis()-start)+" done");
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
class notifyThread implements Runnable{
Integer i;
public notifyThread(Integer i){
super();
this.i = i;
}
@Override
public void run(){
try{
long start = System.currentTimeMillis();
//如果此处设成1500,因为sleep没有占有锁,wait方法在1000ms后会自动再次获得锁然后解除阻塞执行。
Thread.sleep(500);
synchronized(i){
Thread.sleep(1500);
//如果wait过了超时时间,无论有无notify,wait都会自动解除阻塞,即该句可以注释,不影响结果。
//但是如果wait没有设置超时时间,该句必须存在,否则waitThread用于处于阻塞状态。
i.notify();
System.out.println("notifyThread "+(System.currentTimeMillis()-start)+" done");
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
③二者区别
- sleep属于Thread方法,wait属于Object方法
- sleep不会释放锁,wait会让当前线程释放锁
- sleep不用持有锁,wait需要再同步方法或代码块中使用
- sleep使线程进入阻塞状态(醒后进入就绪态等待cpu调度),wait使线程挂起进入等待队列(醒后重新抢锁执行)
- 两者都会被Intertupt()方法中断
(2)join和interrupt
①join()/join(long ms)
1.理解:线程的合并指的是:将指定的线程加入到当前的线程之中,可以将两个交替执行的线程合并为顺序执行的线程,如果在线程B中调用了线程A的Join()方法,B线程挂起等待,直到线程A执行完毕后,才会继续执行线程B。如果设置了超时时间则主线程B不会等到子线程A执行结束,直接继续运行。如果B线程在挂起过程中被调用interrupt,则会抛出InterruptedException异常
2.作用:让“主线程”等待“子线程”结束之后才能继续运行
②interrupt()和isInterrupt()
**1.理解:**首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。而 Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
2.作用:
具体来说,当对一个线程,调用 interrupt() 时,① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常,然后将中断标志设置为false。仅此而已。② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。**3.使用:**interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,需要自己调用isInterrupt()判断是否为true,如果为true那么就可以抛出一个异常让自己中断死亡。
① 在正常运行任务时:经常检查本线程的中断标志位,如果被设置了中断标志就抛出一个InterruptedException异常,自行停止线程。
② 在调用阻塞方法时:正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
package com.sunjs.thread3;
class Sleeper extends Thread {
public Sleeper(String name) {
super(name);
start();//构造函数中启动线程
}
@Override
public void run() {
try {
System.out.println(getName() + " 开始执行");
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " = " + ++i + " = "
+ (isInterrupted() == true ? "要被关闭了" : "我还没有收到关闭通知"));
if (isInterrupted()) {
System.out.println("我收到了要停止的通知了");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
System.out.println("我已经暂停了,下一个被join的线程你执行吧");
}
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();//启动线程
}
@Override
public void run() {
//==========================
//这段代码就是join的主体,如果没有这段代码,那么两个线程启动后,执行顺序将是不可预知的,就是所谓的随机执行。
//如果加入这段代码就是,sleeper线程必须执行完毕退出后,才能执行当前线程,等待的这段时间称为挂起
try {
//joiner线程被挂起
sleeper.join();
} catch (InterruptedException e) {
//如果Joiner线程在被阻塞挂起的时候被调用interrupted方法,则会进入此
e.printStackTrace();
}
//==========================
System.out.println(sleeper.getName() + " ‘执行or停止’ 完毕, " + getName()
+ " 开始执行");
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + " " + i);
}
System.out.println(getName() + " 执行完毕");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper s1 = new Sleeper("s_1");
Joiner j1 = new Joiner("j_1", s1);
s1.interrupt();
}
}
3.守护线程
(1)概念
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
(2)创建
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)。一定要在start之前
daemonThread.setDaemon(true);
daemonThread.start();
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
(3)注意事项
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
(二)并发编程
1.JMM模型:可见性,原子性,有序性
JAVA内存模型,并不真实存在,只是对变量操作的一种规则和约束。
JMM模型规定,java线程在创建时JVM会创建一个线程独有的虚拟机栈,拥有自己的工作内存。当线程使用共享变量时,会先去主内存(堆)中拷贝一份变量到工作内存【之后遇到像加锁、volatile这样的情况才会再次拷贝,不然就一直用工作内存中的】,操作【特指变更操作】完后将结果写回主内存。各个线程之间的工作内存不共享
(1)如何保证可见性,原子性,有序性
- 可见性:volatile,锁机制
- 原子性:锁机制(synchronsize,Lock,atomic)
- 有序性:volatile,锁机制
(2)对同步的规定
- 解锁时,将更改记录刷回主内存
- 加锁时,从主内存复制一份最新数据
- 加解锁要同一把锁
2.volatile
(1)保证可见性:A线程对共享变量操作后,其它线程能立即感知到
public class VolatileTest {
class MyData{
public int number = 0;
public void addTo60(){
this.number = 60;
}
}
public static void main(String[] args) {
MyData myData = new VolatileTest().new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+ "\t come in");
try{
//保证main线程先拿到number
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName()+ "\t update value to 60");
},"AAA").start();
while(myData.number == 0){
//如果main线程从主内存中拿myData对象时,没有感知到里面的number值已经被修改成60,就会一直卡在这
//sout(mydata.number);//注意,如果这里有这一句,因为sout底层会加锁,根据JMM对同步的规定。加锁时从主内存中拷贝最新值,这样就会一直从主内存拿值,就不会卡在这
}
System.out.println(Thread.currentThread().getName()+ "\t feel update");
}
}
(2)不保证原子性
public class AtomicTest {
public volatile static int number = 1;
public static void addPlusPlus(){
number++; //这一步不能保证原子性
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
AtomicTest.addPlusPlus();
}
}).start();
}
while(Thread.activeCount()>2){
//默认main和GC两个线程,等待其他线程执行完毕后,接下去执行
}
System.out.println(AtomicTest.number);
}
}
(3)有序性:禁止指令重排
3.锁机制
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁(进入锁池)会导致其它所有需要此锁的线程挂起(处于挂起状态等待唤醒)。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
独占锁是一种悲观锁((Lock,行锁,表锁,读锁(共享锁),写锁(排他锁))),synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁(Atomic类)。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
3.1悲观锁:Synchronized、AQS
3.1.1Synchronized
(1):用在代码块
public class SynchronizedTest {
public int number = 0;
public void addPlusPlus(){
//this对象可以是任意对象
synchronized (this){
this.number++;
}
}
public static void main(String[] args) {
SynchronizedTest t = new SynchronizedTest();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
t.addPlusPlus();
}
}).start();
}
while(Thread.activeCount()>2){
}
System.out.println(t.number);
}
}
(2):用在方法
package concurrent;
/**
* @author zhangshiqin
* @date 2022/2/15 - 11:29
* @description:
*/
public class SynchronizedTest {
public int number = 0;
//此时锁对象为this。如果为static方法则锁对象为SynchronizedTest类
public synchronized void addPlusPlus(){
this.number++;
}
public static void main(String[] args) {
SynchronizedTest t = new SynchronizedTest();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
t.addPlusPlus();
}
}).start();
}
while(Thread.activeCount()>2){
}
System.out.println(t.number);
}
}
3.1.2AQS(CAS+CLH)
AQS知识1:https://javaguide.cn/java/concurrent/aqs/
AQS知识2:https://blog.csdn.net/u010452388/article/details/90485326
(1)AQS简介:
AQS(AbstractQueuedSynchronizer)是一个抽象队列同步器,位于java.concurrent.locks包下。用于构造锁(ReentrantLock,ReentrantReadWriteLock)和同步器(CountDownLatch,CyclicBarrier,Semaphore)
(2)AQS原理
①:原理概述
AQS规定如果被线程请求的共享资源空闲,那么将当前请求共享资源的线程设置为工作线程并且将共享资源设置为锁定状态(state==1)。当共享资源处于锁定状态时,其余请求共享资源的线程进入阻塞队列等待唤醒。挂起唤醒机制由AQS内置的CLH队列(虚拟双向队列)实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6myrX0q-1686273703942)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220215162017538.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4ul5HJV-1686273703942)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220215162222933.png)]
②:资源共享方式
AQS定义了一个volatile int类型的state来表示同步状态(默认为0),使用 CAS 对该同步状态进行原子操作实现对其值的修改
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Ⅰ.独占锁(Exclusive):ReentrantLock
i.概念:只有一个线程能够执行,又可分为公平锁和非公平锁
ii.ReentrantLock的公平锁和非公平锁(默认):
- 公平锁:new ReentrantLock(true)。根据线程在阻塞队列的排列顺序,先到者先拿锁。
- 非公平锁:当线程要获取锁时,先通过2次CAS操作抢锁。如果没抢到,则进入阻塞队列等待唤醒。
①下面来看 ReentrantLock
中相关的源代码:
ReentrantLock
默认采用非公平锁,因为考虑获得更好的性能,通过 boolean
来决定是否用公平锁(传入 true 用公平锁)。
/** Synchronizer providing all implementation mechanics */
//ReentrantLock实现了Lock
public class ReentrantLock implements Lock, java.io.Serializable {
//我们刚刚要找的sync字段
private final Sync sync;
//Sync继承了AbstractQueuedSynchronizer
abstract static class Sync extends AbstractQueuedSynchronizer {
}
public ReentrantLock() {
// 默认非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
ReentrantLock
中公平锁的 lock
方法
static final class FairSync extends Sync {
//线程会去调用
final void lock() {
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 1. 和非公平锁相比,这里多了一个判断:阻塞队列里是否有线程在等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
非公平锁的 lock
方法:
static final class NonfairSync extends Sync {
final void lock() {
// 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 这里没有对阻塞队列进行判断
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//因为是重入,是同一个线程,所以会执行下面的代码
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
②区别:公平锁和非公平锁只有两处不同
- 线程调用lock()方法后,非公平锁会先进行一次CAS拿锁,成功则直接返回
- 如果非公平锁第一次CAS抢锁失败,和公平锁一样进入
tryAcquire(1)
方法,在tryAcquire
方法里,如果发现锁这个时候被释放了(state == 0)
,非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果没有则代表当前线程是排在第一位,进行CAS抢锁。如果非公平锁两次CAS抢锁都失败则之后的过程和公平锁一样进入阻塞队列等待唤醒(自旋判断state是否==0)
③总结:
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大(低延迟)。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
iii.ReentrantLock是可重入锁
-
概念:重入锁的意思就是,线程thread1执行ReentrantLock的lock()方法获取到锁之后且没有释放锁,再次调用lock()方法的时候,不会阻塞,直接增加重入次数
-
目的:如果调用出现阻塞,就会出现死锁。所以可重入锁是为了解决死锁
-
代码:
//因为是重入,是同一个线程,所以会执行下面的代码
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
iiii.获取锁的四种方法:
(1)lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
(2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
(3)tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
(4)lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果有线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。样例:https://www.bbsmax.com/A/kmzLb8Mb5G/
Ⅱ.共享锁(Share):ReentrantReadWriteLock,CountDownLatch,CyclicBarrier,Semaphore
i.概念:线程可以共享执行
ii.ReentrantReadWriteLock(读写锁)
https://www.cnblogs.com/xiaoxi/p/9140541.html
-
架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bVF24fEs-1686273703943)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221105706163.png)]
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { /** 读锁 */ private final ReentrantReadWriteLock.ReadLock readerLock; /** 写锁 */ private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync; /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */ public ReentrantReadWriteLock() { this(false); } /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */ public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } /** 返回用于写入操作的锁 */ public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } /** 返回用于读取操作的锁 */ public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } abstract static class Sync extends AbstractQueuedSynchronizer {} static final class NonfairSync extends Sync {} static final class FairSync extends Sync {} public static class ReadLock implements Lock, java.io.Serializable {} public static class WriteLock implements Lock, java.io.Serializable {} }
-
概念:它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。进入这两个锁需要满足下列条件,即不支持读写锁,写写锁共存
- 进入读锁之前:
- 没有其他线程的写锁
- 有写锁,但是调用读锁线程和持有写锁线程是同一个(锁降级)
- 进入写锁之前:
- 没有其他线程的读锁
- 没有其他线程的写锁
- 进入读锁之前:
-
特性:
-
支持公平/非公平(默认)方式
-
可重入锁
-
支持锁获取期间的中断
-
boolean tryLock; boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
-
lockInterruptibly()
-
-
锁降级:即先获取写锁->再获取读锁->最后释放写锁
-
Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。
-
-
原理:
-
ReentrantReadWriteLock 也是基于AQS实现的,它的自定义同步器sync(继承AQS)需要在同步状态(一个整型变量state)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。例如,如果读锁申请线程数量为13【最大为65535】,写锁申请线程数量为1,则state表示为851969
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1OKYHnl-1686273703943)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221133528101.png)]
-
写锁的获取与释放
-
写锁的获取tryAcquire(1)
- new ReentrantLock(Boolean fair).writeLock()
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } /** * Nonfair version of Sync */ static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean writerShouldBlock() { return false; // writers can always } final boolean readerShouldBlock() { return apparentlyFirstQueuedIsExclusive(); } }
- 调用Lock()方法
public void lock() { sync.acquire(1); }
- 调用tryAcquire(1):如果返回false则进入阻塞队列
protected final boolean tryAcquire(int acquires) { //当前线程 Thread current = Thread.currentThread(); //获取状态 int c = getState(); //写线程数量(即获取独占锁的重入数) int w = exclusiveCount(c); //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁 if (c != 0) { // 当前state不为0,此时: // 如果写锁状态为0说明读锁此时被占用返回false(写锁之前读锁要释放); // 如果写锁状态不为0且写锁没有被当前线程持有返回false(说明写锁被占用); if (w == 0 || current != getExclusiveOwnerThread()) return false; //判断同一线程获取写锁是否超过最大次数(65535),支持可重入 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); //更新状态 //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。 setState(c + acquires); return true; } //到这里说明此时c=0,读锁和写锁都没有被获取 //writerShouldBlock表示是否阻塞,如果是非公平锁返回false if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //设置锁为当前线程所有 setExclusiveOwnerThread(current); return true; }
- 总结:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hy0jGwMO-1686273703944)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221142653588.png)]
-
写锁的释放tryRelease(1)
//releases = 1 protected final boolean tryRelease(int releases) { //若锁的持有者不是当前线程,抛出异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //写锁的新线程数 int nextc = getState() - releases; //如果独占模式重入数为0了,说明独占模式被释放 boolean free = exclusiveCount(nextc) == 0; if (free) //若写锁的新线程数为0,则将锁的持有者设置为null setExclusiveOwnerThread(null); //设置写锁的新线程数 //不管独占模式是否被释放,更新独占重入数 setState(nextc); return free; }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfLsO9qU-1686273703944)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221144006118.png)]
-
-
读锁的获取与释放
-
读锁的获取tryAcquireShared(1)
protected final int tryAcquireShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); // 获取状态 int c = getState(); //如果写锁线程数 != 0 ,且独占锁不是当前线程则返回失败,因为存在锁降级 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 读锁数量 int r = sharedCount(c); /* * readerShouldBlock():读锁是否需要等待(公平锁原则) * r < MAX_COUNT:持有线程小于最大数(65535) * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态 */ // 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中 if (r == 0) { // 读锁数量为0 // 设置第一个读线程 firstReader = current; // 读线程占用的资源数为1 firstReaderHoldCount = 1; } else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入 // 占用资源数加1 firstReaderHoldCount++; } else { // 读锁数量不为0并且不为当前线程 // 获取计数器 HoldCounter rh = cachedHoldCounter; // 计数器为空或者计数器的tid不为当前正在运行的线程的tid if (rh == null || rh.tid != getThreadId(current)) // 获取当前线程对应的计数器 cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) // 计数为0 //加入到readHolds中 readHolds.set(rh); //计数+1 rh.count++; } return 1; } return fullTryAcquireShared(current); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LjYETuqt-1686273703945)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221144131697.png)]
更新成功后会在firstReaderHoldCount中或readHolds(ThreadLocal类型的)的本线程副本中记录当前线程重入数(23行至43行代码),这是为了实现jdk1.6中加入的getReadHoldCount()方法的,这个方法能获取当前线程重入共享锁的次数(state中记录的是多个线程的总重入次数),加入了这个方法让代码复杂了不少,但是其原理还是很简单的:如果当前只有一个线程的话,还不需要动用ThreadLocal,直接往firstReaderHoldCount这个成员变量里存重入数,当有第二个线程来的时候,就要动用ThreadLocal变量readHolds了,每个线程拥有自己的副本,用来保存自己的重入数。
final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { // 无限循环 // 获取状态 int c = getState(); if (exclusiveCount(c) != 0) { // 写线程数量不为0 if (getExclusiveOwnerThread() != current) // 不为当前线程 return -1; } else if (readerShouldBlock()) { // 写线程数量为0并且读线程被阻塞 // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // 当前线程为第一个读线程 // assert firstReaderHoldCount > 0; } else { // 当前线程不为第一个读线程 if (rh == null) { // 计数器不为空 // rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { // 计数器为空或者计数器的tid不为当前正在运行的线程的tid rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) // 读锁数量为最大值,抛出异常 throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { // 比较并且设置成功 if (sharedCount(c) == 0) { // 读线程数量为0 // 设置第一个读线程 firstReader = current; // firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
-
读锁的释放tryReleaseShared(1)
protected final boolean tryReleaseShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); if (firstReader == current) { // 当前线程为第一个读线程 // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) // 读线程占用的资源数为1 firstReader = null; else // 减少占用的资源 firstReaderHoldCount--; } else { // 当前线程不为第一个读线程 // 获取缓存的计数器 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) // 计数器为空或者计数器的tid不为当前正在运行的线程的tid // 获取当前线程对应的计数器 rh = readHolds.get(); // 获取计数 int count = rh.count; if (count <= 1) { // 计数小于等于1 // 移除 readHolds.remove(); if (count <= 0) // 计数小于等于0,抛出异常 throw unmatchedUnlockException(); } // 减少计数 --rh.count; } for (;;) { // 无限循环 // 获取状态 int c = getState(); // 获取状态 int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // 比较并进行设置 // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8PnbdV3A-1686273703945)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220221144208727.png)]
-
-
-
代码样例:
- 读读并发:利用重入来执行升级缓存后的锁降级
class CachedData { //被缓存的具体对象 Object data; //当前对象是否可用,使用volatile来保证可见性 volatile boolean cacheValid; //今天的主角,ReentrantReadWriteLock final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); //业务处理逻辑 void processCachedData() { //要读取数据时,先加读锁,如果加成功,说明此时没有人在并发写 rwl.readLock().lock(); //拿到读锁后,判断当前对象是否有效 if (!cacheValid) { // Must release read lock before acquiring write lock //这里的处理非常经典,当你持有读锁之后,不能直接获取写锁, //因为写锁是独占锁,如果直接获取写锁,那代码就在这里死锁了 //所以必须要先释放读锁,然后手动获取写锁 rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. //经典处理之二,在独占锁内部要处理数据时,一定要做二次校验 //因为可能同时有多个线程全都在获取写锁, //当时线程1释放写锁之后,线程2马上获取到写锁,此时如果不做二次校验那可能就导致某些操作做了多次 if (!cacheValid) { data = ... //当缓存对象更新成功后,重置标记为true cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock //这里有一个非常神奇的锁降级操作,所谓降级是说当你持有写锁后,可以再次获取读锁 //这里之所以要获取一次写锁是为了防止当前线程释放写锁之后,其他线程马上获取到写锁,改变缓存对象 //因为读写互斥,所以有了这个读锁之后,在读锁释放之前,别的线程是无法修改缓存对象的 rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } } }
- 读写互斥:Collection+ReentrantReadWriteLock 构造并发集合容器
class RWDictionary { //原来非并发安全的容器 private final Map<String, Data> m = new TreeMap<String, Data>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Data get(String key) { //读数据,上读锁 r.lock(); try { return m.get(key); } finally { r.unlock(); } } public String[] allKeys() { //读数据,上读锁 r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); } } public Data put(String key, Data value) { //写数据,上写锁 w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { //写数据,上写锁 w.lock(); try { m.clear(); } finally { w.unlock(); } } } class RWDictionary { //原来非并发安全的容器 private final Map<String, Data> m = new TreeMap<String, Data>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Data get(String key) { //读数据,上读锁 r.lock(); try { return m.get(key); } finally { r.unlock(); } } public String[] allKeys() { //读数据,上读锁 r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); } } public Data put(String key, Data value) { //写数据,上写锁 w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { //写数据,上写锁 w.lock(); try { m.clear(); } finally { w.unlock(); } } }
iii.CountDownLatch(倒计时)
- 使用场景:
- 某一线程在开始运行前等待 n 个线程执行完毕。
- 多个线程并行开始执行任务。
- 原理:new CountDownLatch(int count)
将state的值设置为count,当调用countDown时,state减少1。调用await时会去判断state是否为0,如果不为0则会一直阻塞直到为0。CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次
- 常用方法:
- countDown():state - 1
- await():阻塞,当state == 0时放开
- getCount():返回当前的state
- 代码样例:
- 主线程等待所有子线程执行完毕后再执行
- 多个线程并行开始执行任务
/**
* @author zhangshiqin
* @date 2022/2/17 - 16:34
* @description:主线程等待所有子线程执行完毕后再执行
*/
public class CountDownLatchTest {
//请求数量,500个
private static final int REQUEST_COUNT = 500;
public static void main(String[] args) {
//计数器,state设置为等于请求量
CountDownLatch latch = new CountDownLatch(REQUEST_COUNT);
//线程池
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < REQUEST_COUNT; i++) {
final int requestNumber = i;
executor.submit(()->{
handlerRequest(requestNumber);
//执行完后让state-1,当前线程执行完毕
latch.countDown();
});
}
try {
//主线程会一直在这里等待,知道state=0
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+" finish");
}
static void handlerRequest(int requestNumber){
System.out.println("handler: "+requestNumber);
}
}
/**
* @author zhangshiqin
* @date 2022/2/17 - 16:34
* @description:线程并行开始执行任务
*/
public class CountDownLatchTest {
//赛马,10个
private static final int HORSE = 10;
public static void main(String[] args) throws InterruptedException {
//计数器,state设置为等于请求量
CountDownLatch beginLatch = new CountDownLatch(1);
CountDownLatch readyLatch = new CountDownLatch(HORSE);
for (int i = 0; i < HORSE; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 准备就绪...");
try {
readyLatch.countDown();
beginLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 到终点!!!");
},i+"号马").start();
}
readyLatch.await();
System.out.println("\n比赛开始。。。。。。。\n");
beginLatch.countDown();
}
}
iiii.CyclicBarrier(循环栅栏)
- 使用场景:合并多个线程计算结果
- 原理:new CyclicBarrier
public CyclicBarrier(int parties) {
this(parties, null);
}
//barrierAction操作由最后一个进入 barrier 的线程执行
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
将state的值设置为parties,parties表示屏障拦截的线程数量,每个线程调用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。每当有一个线程到达时,state-1,直到state==0时,放行这一组的线程,然后将state重置为parties,因此CyclicBarrier是循环多次的。
⭐CountDownLatch与CyclicBarrier的区别:
* CountDownLatch用于一个线程/多个线程等待其余n个线程,只能使用一次
* CyclicBarrier用于多个线程之间的互相等待,并且它会reset可以重复使用
- 常用方法:await()
- 代码样例:
/**
* @author zhangshiqin
* @date 2022/2/18 - 15:11
* @description:多个线程合并计算结果
*/
public class CyclicBarrierTest {
private static final int THREAD_COUNT = 10;
public static void main(String[] args) {
Lock lock = new ReentrantLock();
List<Integer> list = new ArrayList<>();
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT,()->{
System.out.println(Thread.currentThread().getName()+ " 计算得出合计结果为:" +list.stream().reduce(Integer::sum));
});
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(()->{
lock.lock();
int result = (int) (Math.random() * 30 + 1);
list.add(result);
System.out.println(Thread.currentThread().getName()+ " 计算结果为:"+result);
lock.unlock();
try {
//互相等待
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},i+"").start();
}
}
}
iiiii.Semaphore(信号量)
- 使用场景:限制访问共享资源的线程数量
- 原理:new Semaphore
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
默认构造 AQS 的 state 为 permits。 acquire() 方法使得state-1,这个方法是一个阻塞方法,当执行任务的线程数量超出 permits,那么多余的线程将会被放入阻塞队列 ,并自旋判断 state 是否大于 0。只有当 state 大于 0 的时候,阻塞的线程才能继续执行,此时先前执行任务的线程继续执行 release() 方法,release() 方法使得 state 的变量会加 1,那么自旋的线程便会判断成功。如此,每次只有最多不超过 permits 数量的线程能自旋成功,便限制了执行任务线程的数量。
-
常用方法:
- acquire()/acquire(int count):count代表一次拿几个许可证。state -1 / state - count
- tryAcquire():如果获取不到,直接返回false【可以做判断如果为false则进行其他操作,比如提示用户拒绝访问】
- release():state + 1
-
代码样例:
public class SemaphoreTest {
//模拟500个请求
private static int request = 500;
public static void main(String[] args) {
//信号量同步器,state值设置为20
final Semaphore semaphore = new Semaphore(20);
//线程池,放100个线程去处理请求
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < SemaphoreTest.request; i++) {
final int requestNumber = i;
executor.submit(()->{
try {
semaphore.acquire();//state = state -1。剩余许可证数29。该方法为阻塞方法
handlerRequest(requestNumber);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。此处是异步操作不会阻塞
executor.shutdown();
while(Thread.activeCount() > 2){
}
System.out.println("finish");
}
public static void handlerRequest(int requestNumber){
System.out.println("requestNumber: "+requestNumber);
}
}
③:底层模板方法模式自定义同步器
Ⅰ钩子方法:
protected boolean tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected boolean tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
Ⅱ如何自定义同步器:
以
ReentrantLock
为例,state 初始化为 0,表示未锁定状态。A 线程lock()
时,会调用tryAcquire()
独占该锁并将state+1
。此后,其他线程再tryAcquire()
时就会失败,直到 A 线程unlock()
到state=
0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。再以
CountDownLatch
以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后countDown()
一次,state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即state=0
),会unpark()
主调用线程,然后主调用线程就会从await()
函数返回,继续后余动作。一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现
tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock
。
3.2乐观锁:Atomic类(基于CAS理论实现)
3.2.1CAS(unsafe类+自旋)
(1)概念和原理:CAS(Compare And Swap),是在硬件层面比较并交换来保证原子性(unsafe类)。包含3个操作数,V内存值,A旧预期值,B要修改值,底层通过不断的while自旋来判断是否能够修改值。
(2)优缺点:
- 优点:在代码,线程层面无锁机制,将锁的管理交给硬件,提高效率
- 缺点:
- 如果在竞争激烈的情况下,长时间自旋消耗CPU大
- ABA问题:A->B->A,认为没改。解决:AtomicStampedReference类,该类带上版本号
3.2.2Atomic类
- 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
4.线程池
(1)架构图:
Executor->ExecutorService->AbstractExecutorService->ThreadPoolExecutor(核心类,底层)
(2)常用方法(都不用):Executors.方法名
方法名 | 功能 |
---|---|
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newCachedThreadPool() | 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行 |
(3)七大参数:
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
-
corePoolSize:核心线程数,线程池中核心的线程数,即“今日当值人员”
-
maximumPoolSize:最大线程数,当核心线程数和阻塞队列都满后,开启max-core剩余的非核心线程
-
keepAliveTime:多余空闲非核心线程存活的时间
-
TimeUnit unit:超时时间的单位
-
BlockingQueue workQueue:阻塞队列,队列长度代表能放置阻塞的任务数
-
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;
-
-
ThreadFactory threadFactory:线程工厂,用来创建线程
-
Executors.defaultThreadFactory()
-
-
RejectedExecutionHandler handler:拒绝策略,当阻塞队列和maximumPoolSize都满了后执行的拒绝任务策略
-
new ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常。 new ThreadPoolExecutor.DiscardPolicy():也是丢弃任务,但是不抛出异常。 new ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) new ThreadPoolExecutor.CallerRunsPolicy():将任务抛给调用线程池的线程,一般是主线程
-
(4)工作原理:
①线程池状态
volatile int runState;//当前线程池的状态
static final int RUNNING = 0;//线程池创建后处于RUNNING状态
static final int SHUTDOWN = 1;//线程池调用shutdown()方法后处于SHUTDOWN状态,不接收新任务,异步继续执行已有任务(包括任务队列里阻塞的)
static final int STOP = 2;//线程池调用shutdownNow()方法后处于STOP状态,不接收新任务,尝试终止当前正在执行的任务(循环遍历线程并调用它们的interrupted()方法),返回任务队列里的未执行任务。
static final int TERMINATED = 3;//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
* shutdown()和shutdownNow()的区别:https://www.cnblogs.com/qingquanzi/p/9018627.html
②任务的执行
- 创建线程池后,等待任务的提交,此时还没有创建线程
- 调用execute()或submit()方法创建线程执行任务,假设当前正在执行任务的线程数为n
- 如果n<core,直接执行任务
- 如果n>=core,进入任务队列等待
- 如果n>=core && 任务队列满 && n<max,创建非核心线程立即执行任务
- 如果n>=core && 任务队列满 && n>=max,拒绝策略
- 如果有线程完成了任务,会从任务队列中拿一位任务来执行(FIFO策略,先进先拿)
- 如果多余空闲的非核心线程在超过keepAliveTime后,如果n>core,会暂时关闭非核心线程
③如何正确关闭线程池:https://www.cnblogs.com/qingquanzi/p/9018627.html
总结:shutdownNow()和shutdown()都是异步关闭,都不会立刻关闭线程池,要等到池子里所有线程都关闭了线程池才关闭。如果要阻塞等待线程池关闭,需要在这之后调用awaitTermination()方法
- shutdownNow():能够返回不再处理的任务
- 拒绝接收新任务,设置线程池状态为STOP,调用所有线程的interrupted()方法
- 对于工作队列中等待的任务和正在进行中的任务做如下处理
- 不再拉取工作队列中等待的任务。除非线程正在拉取工作队列中的任务并且已拉取成功,则继续执行这个任务。
- 正常运行的任务会继续进行(代码里可以调用isInterrupted决定要不要继续运行)
- 阻塞中的任务会直接抛InterruptedException
- shutdown():
- 拒绝接收新任务,设置线程池状态为SHUTDOWN,调用没在处理任务的线程(空闲线程)的interrupted()方法
- 对于工作队列中等待的任务和正在进行中的任务都会继续进行处理
5.ThreadLocal
⭐线程工作内存:理解为虚拟机栈。线程本地内存(堆):ThreadLocal.ThreadLocalMap。TLAB(堆):Thread-Local-Allocated-Buffer(本地线程分配缓冲区)
(1)ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本(存在本线程的工作内存里),在实际多线程操作ThreadLocal变量的时候,与其他拷贝的变量是不同的,操作的是线程自己本地内存(ThreadLocal.ThreadLocalMap)中的变量,从而规避了线程安全问题,如下图所示
⭐new 一个ThreadLocal,然后set值,那么在Thread的threadLocals(ThreadLocalMap)中就会产生一个Entry与之对应。ThreadLocalMap中有Entry[] table来存储所有的Entry
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTcMjOPg-1686273703946)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220223162019971.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xn75uEF-1686273703946)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220224094016109.png)]
(2)ThreadLocal源码分析
- new ThreadLocal<>()
//这是一个支持泛型的类
public class ThreadLocal<T> {
//每new一个ThreadLocal对象就会调用一次nextHashCode()
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
//类变量,随着每次set数据都会加上一个0x61c88647
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//静态内部类ThreadLocalMap
static class ThreadLocalMap {
private Entry[] table;
//Entry[]里元素的数量
private int size;
//静态内部类Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
//代码样例
public class ThreadLocalDemo {
public static void main(String[] args){
new Thread(()->{
test("test");
},"thread_A").start();
}
public static void test(String args){
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set(args);//入口
}
}
- 接下来看看threadLocal.set(args);这一步做了什么操作
//这个泛型T,等于ThreadLocal<T>的T
public void set(T value) {
//拿到当前线程,即thread_A线程
Thread t = Thread.currentThread();
//拿到当前线程的本地内存,即ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- 看看getMap(t)会发生什么
//Thread类
public class Thread{
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap getMap(Thread t) {
//将thread_A线程的ThreadLocal.ThreadLocalMap threadLocals变量赋值给Set方法中的ThreadLocalMap map
return t.threadLocals;
}
- 接下来如果map为null,则进入createMap(t,value)
//此处this为ThreadLocal<String>对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//接下来将会为threadLocals赋值
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//默认数组长度为16
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;//Entry[] tables的大小
setThreshold(INITIAL_CAPACITY);//这一步为之后得扩容做准备,threshold = len*2/3
}
//new Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//调用父类构造器,最终这个key指向ThreadLocal对象
super(k);
value = v;
}
}
(3)ThreadLocal的Hash算法
斐波那契数:0x61c88647
- 首先让我们看看代码如何实现hash算法,
threadLocalHashCode
为每个ThreadLocal对象独有,在new ThreadLocal<>()
时计算出,这个值在对象存在期间不会变化
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
}
(4)ThreadLocal如何解决Hash冲突以及过期key的清理
探测式清理(清理+桶整理):set(),get(),rehash()、启发式清理:set()
触发条件:1.碰到过期key或者2.判断扩容和3.扩容时
①set()探测式清理——int expuseStaleEntry(int slotToExpuse)
-
如果hash计算得出的最新下标 i 所在得Entry为null,则直接把新Entry对象e放进去,然后判断是否扩容
-
如果hash计算得出得最新下标 i 所在的Entry不为null
-
如果Entry[i]的key和e.get()得出的key一样,那么直接做value的替换
-
如果不一样,则从当前下标开始往后找
-
如果找到一个Entry为null,则直接将新Entry对象e放进去,然后进行数据清理和判断是否扩容
-
如果在找到一个Entry为null之前,找到了一个Entry.key为null的过期数据,则开始进行数据清理
初始slotToExpuse = staleSlot,staleSlot的值为Entry.key的下标
- 从staleSlot先向前每次查找,如果找到有key==null的数据,则将slotToExpuse的值更新。直到Entry为null
- 然后开始从staleSlot向后查找,直到Entry为null,返回当前的下标
- 如果找到key一样的entry,则直接交替Entry但是staleSlot位置不变,判断
slotToExpuse==staleSlot
,如果为true
则代表向前查找没有key为null的Entry,此时要将slotToExpuse=i,i为当前查找的下标
。之后开始expungeStaleEntry(int slotToExpuse)- 先将slotToExpuse下标所在的Entry清理,然后开始向后查找
- 如果key为null,则把当前下标的Entry置为null
- 否则进行rehash,即把当前Entry[]进行一次桶整理:找到这个ThreadLocal对象原本应该在的位置,如果这个位置Entry现在为null,则直接替换,否则往后找最近的
- 先将slotToExpuse下标所在的Entry清理,然后开始向后查找
- 如果找到key为null的,则继续判断
slotToExpuse==staleSlot
,如果都为true
则代表向前查找没有key为null的Entry,此时要将slotToExpuse=i,i为当前查找的下标
。之后开始expuseStaleEntries(int slotToExpuse) - 如果Entry为null了,那么就将
Entry[staleSlot]
赋值,判断slotToExpuse!=staleSlot
成立(代表之前向前找有找到过期key),之后开始expungeStaleEntry(int slotToExpuse)
- 如果找到key一样的entry,则直接交替Entry但是staleSlot位置不变,判断
-
-
- 首先我们从入口方法,ThreadLocalMap的set方法来一步步看如何实现
//map.set(this,value)后进入这一方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;//当前线程得threadLocalMap
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);//通过hash计算的出哈希表数组新的下标
//循环遍历来把一个新的Entry e插入到合适得位置,直到Entry为null推出循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果当前位置Entry不为null,并且Entry的k和要插入的Entry的key相同,则直接替换值
if (k == key) {
e.value = value;
return;
}
//如果当前位置为过期Entry(即key为null),则进入过期清理阶段
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果当前位置Entry为null,则直接插入
tab[i] = new Entry(key, value);
int sz = ++size; //Entry[]里的元素数量+1
//进行一次启发式清理,如果没有东西清理了 并且当前元素数量大于Entry[].length的2/3时,进入扩容阶段
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();//进入这一步后并不是真正的扩容,我们在接下去分析
}
- 接下来我们看看replaceStaleEntry(key, value, i);里面发生了什么来解决冲突
//staleSlot,标记过期key的位置
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//先将待会的清理标致等于当前过期key下标
int slotToExpunge = staleSlot;
//从当前过期key往前遍历,直到Entry为null停止,如果找到新的Entry.key为null的,则slotToExpunge=i
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//接下来往后遍历,同样的当Entry为null时停止
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果找到一个Entry的key和新Entry的key相同
if (k == key) {
e.value = value;
//直接将当前位置Entry替换为过期Entry
tab[i] = tab[staleSlot];
//将过期Entry替换为新Entry
tab[staleSlot] = e;
//判断如果上一步往前查找过期Entry没有找到,则把slotToExpunge设置为当前遍历下标
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//开始探测式清理,启发式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果找到一个过期Entry,则判断一下上一步向前找是否有找到过期Entry,如果没有则把slotToExpunge设置为当前遍历下标
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//向后遍历直到Entry为null,那么就会给过期标志位staleSlot赋值
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//判断一下上一步向前找是否有找到过期Entry,如果找到了就开始探测式清理、启发式清理
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
- 最后看看expungeStaleEntry(int slotToExpuse)
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 先把开始下标位置的过期key清除
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
}
//如果没有找到过期key,则开始桶整理,原理就是找到这个ThreadLocal对象本来应该在的哈希表位置,然后看当前这个位置有没有Entry,如果为null直接两者Entry替换,否则就继续往后找到里最近的空位置(Entry为null)
else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//清理过程中碰到Entry为null则直接返回
return i;
}
②get()探测式清理————int expuseStaleEntry(int slotToExpuse)
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
③启发式清理——boolean cleanSomeSlots(int startIndex,int length)
- 如果长度为16,则会遍历5次。如果为32(扩容机制翻一倍)则遍历6次。以此类推
- 启发式清理的原理还是探测式清理,即启发式清理会调度探测式清理
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
(5)ThreadLocal扩容机制
rehash()=>size >= threshold=length*2/3
👇
expuseStaleEntries()
👇
resize()=>size >= threshold * 3/4
👇
长度在基础上翻一倍
(三)实现生产者消费者模式
1.Synchronized+wait+notify:存在虚假唤醒,使用while做自旋判断
- 仓库类
package com.newland.paas.paasservice.externalapi.paasnaiservice.Thread;
import java.util.LinkedList;
import java.util.Queue;
/**
* @author zhangshiqin
* @date 2022/2/24 - 10:57
* @description:仓库类,生产者和消费者共享内存区域
*/
public class Warehouse<T> {
private Queue<T> goods;
public Queue<T> getGoods() {
return goods;
}
public void setGoods(Queue<T> goods) {
this.goods = goods;
}
public Warehouse(){
this.goods = new LinkedList<>();
}
@Override
public String toString() {
return "Warehouse{" +
"goods=" + goods.toString() +
'}';
}
}
- 生产者:锁对象和消费者一样
public class Producers<T> {
private final Warehouse<T> warehouse;
//指定生产者对应的仓库
public Producers(Warehouse<T> warehouse){
this.warehouse = warehouse;
}
//向仓库添加货物
public void addToWarehouse(T goods){
Thread t = Thread.currentThread();
synchronized (warehouse){
//使用while防止虚假唤醒,因为if只会进入一次
while(warehouse.getGoods().size()==10){
try {
System.out.println(t.getName()+"等待。。。");
warehouse.wait();
} catch (InterruptedException e) {
if(t.isInterrupted()){
//处理业务逻辑
System.out.println(t.getName()+"被终止。。。");
}
e.printStackTrace();
}
}
warehouse.getGoods().add(goods);
System.out.println(t.getName()+" 向仓库存放货物:"+goods+",总量:"+warehouse.getGoods().size());
warehouse.notifyAll();
}
}
//常量没有setter
public Warehouse<T> getWarehouse() {
return warehouse;
}
}
- 消费者:锁对象和生产者一样
public class Consumer {
public <T> T getFromWarehouse(Warehouse<T> warehouse){
Thread t = Thread.currentThread();
synchronized (warehouse){
//如果为0代表此时仓库中没有物品,线程挂起
while(warehouse.getGoods().size()==0){
try {
System.out.println(t.getName()+"等待。。。");
warehouse.wait();
} catch (InterruptedException e) {
if(t.isInterrupted()){
//处理业务逻辑
System.out.println(t.getName()+"被终止。。。");
}
e.printStackTrace();
}
}
T remove = warehouse.getGoods().poll();
System.out.println(t.getName()+" 取出一个货品:"+remove);
warehouse.notifyAll();
return remove;
}
}
}
- 测试类
public class Test {
public static void main(String[] args) {
Warehouse<Integer> warehouse = new Warehouse<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
Producers<Integer> producer = new Producers<>(warehouse);
producer.addToWarehouse((int) (Math.random() * 100));
},i+"-producers").start();
new Thread(()->{
Consumer consumer = new Consumer();
consumer.getFromWarehouse(warehouse);
},i+"-consumer").start();
}
}
}
###输出结果###
0-consumer等待。。。
0-producers 向仓库存放货物:35,总量:1
2-consumer 取出一个货品:35
2-producers 向仓库存放货物:19,总量:1
3-producers 向仓库存放货物:12,总量:2
1-producers 向仓库存放货物:57,总量:3
1-consumer 取出一个货品:19
6-consumer 取出一个货品:12
6-producers 向仓库存放货物:49,总量:2
4-consumer 取出一个货品:57
7-producers 向仓库存放货物:73,总量:2
8-producers 向仓库存放货物:97,总量:3
9-producers 向仓库存放货物:73,总量:4
9-consumer 取出一个货品:49
0-consumer 取出一个货品:73
8-consumer 取出一个货品:97
4-producers 向仓库存放货物:36,总量:2
5-consumer 取出一个货品:73
5-producers 向仓库存放货物:68,总量:2
7-consumer 取出一个货品:36
3-consumer 取出一个货品:68
2.Lock+Condition(await()+signal())
1、await():导致当前线程等待,直到其他线程调用该Condition的signal()或signalAll()方法唤醒该线程。
2、signal():唤醒在此Lock对象上等待的单个线程。
3、signalAll():唤醒在此Lock对象上等待的所有线程。
- 生产者代码
public class Producers<T> {
private final Warehouse<T> warehouse;
private final Lock lock;
//指定生产者对应的仓库
public Producers(Warehouse<T> warehouse){
this.warehouse = warehouse;
this.lock = warehouse.getLock();
}
//向仓库添加货物
public void addToWarehouse(T goods){
Thread t = Thread.currentThread();
Condition condition = warehouse.getCondition();
lock.lock();
while(warehouse.getGoods().size()==10){
try {
System.out.println(t.getName()+"等待。。。");
condition.await();
} catch (InterruptedException e) {
if(t.isInterrupted()){
//处理业务逻辑
System.out.println(t.getName()+"被终止。。。");
}
e.printStackTrace();
}
}
warehouse.getGoods().add(goods);
System.out.println(t.getName()+" 向仓库存放货物:"+goods+",总量:"+warehouse.getGoods().size());
condition.signalAll();
lock.unlock();
}
//常量没有setter
public Warehouse<T> getWarehouse() {
return warehouse;
}
}
- 消费者代码
public class Consumer {
public <T> T getFromWarehouse(Warehouse<T> warehouse){
Thread t = Thread.currentThread();
Lock lock = warehouse.getLock();
Condition condition = warehouse.getCondition();
lock.lock();
//如果为0代表此时仓库中没有物品,线程挂起
while(warehouse.getGoods().size()==0){
try {
System.out.println(t.getName()+"等待。。。");
condition.await();
} catch (InterruptedException e) {
if(t.isInterrupted()){
//处理业务逻辑
System.out.println(t.getName()+"被终止。。。");
}
e.printStackTrace();
}
}
T remove = warehouse.getGoods().poll();
System.out.println(t.getName()+" 取出一个货品:"+remove);
condition.signalAll();
lock.unlock();
return remove;
}
}
- 仓库
public class Warehouse<T> {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Queue<T> goods;
public Queue<T> getGoods() {
return goods;
}
public Condition getCondition() {
return condition;
}
public void setCondition(Condition condition) {
this.condition = condition;
}
public void setGoods(Queue<T> goods) {
this.goods = goods;
}
public Warehouse(){
this.goods = new LinkedList<>();
}
public Lock getLock() {
return lock;
}
public void setLock(Lock lock) {
this.lock = lock;
}
@Override
public String toString() {
return "Warehouse{" +
"goods=" + goods.toString() +
'}';
}
}
3.BlockingQueue+Atomic
(1)BlockingQueue阻塞队列数据结构
①四组API
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
四组不同的行为方式解释:
1(异常)
如果试图的操作无法立即执行,抛一个异常。比如队列满时,插入一个元素
2(特定值)
如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3(阻塞)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4(超时)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
②注意事项
BlockingQueue :不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
BlockingQueue: 可以是限定容量的,不扩容的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告Integer.MAX_VALUE 的剩余容量。
BlockingQueue :实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue :实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll© 有可能失败(抛出一个异常)。
③常用实现类
- ArrayBlockingQueue
- 有界阻塞队列,构造时必须指定队列长度
- 此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”
- LinkedBlockingQueue
- 可以是有界阻塞队列,默认Integer.MAX_VALUE
- SynchronousQueue
- 容量为1的同步队列
- SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。
(2)代码样例
//生产者
public class Producers<T> {
private final Warehouse<T> warehouse;
//指定生产者对应的仓库
public Producers(Warehouse<T> warehouse){
this.warehouse = warehouse;
}
//向仓库添加货物
public void addToWarehouse(T good){
Thread t = Thread.currentThread();
BlockingQueue<T> goods = warehouse.getGoods();
while(true){
try {
boolean offer = goods.offer(good,100,TimeUnit.MILLISECONDS);
if(offer){
break;
}else {
System.out.println(t.getName()+ " 等待。。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(t.getName()+" 向仓库存放货物:"+goods+",总量:"+warehouse.getGoods().size());
}
//常量没有setter
public Warehouse<T> getWarehouse() {
return warehouse;
}
}
//消费者
public class Consumer {
public <T> T getFromWarehouse(Warehouse<T> warehouse){
Thread t = Thread.currentThread();
BlockingQueue<T> goods = warehouse.getGoods();
T element;
try {
while(true){
element = goods.poll(100, TimeUnit.MILLISECONDS);
if(element==null){
System.out.println(t.getName()+" 等待。。。");
}else {
break;
}
}
System.out.println(t.getName()+" 取到一个元素:"+element.toString());
return element;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
//仓库类
public class Warehouse<T> {
private BlockingQueue<T> goods;
public Warehouse(){
this.goods = new ArrayBlockingQueue<>(10);
}
public BlockingQueue<T> getGoods() {
return goods;
}
public void setGoods(BlockingQueue<T> goods) {
this.goods = goods;
}
@Override
public String toString() {
return "Warehouse{" +
"goods=" + goods.toString() +
'}';
}
}
gQueue
- 可以是有界阻塞队列,默认Integer.MAX_VALUE
- SynchronousQueue
- 容量为1的同步队列
- SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。
(2)代码样例
//生产者
public class Producers<T> {
private final Warehouse<T> warehouse;
//指定生产者对应的仓库
public Producers(Warehouse<T> warehouse){
this.warehouse = warehouse;
}
//向仓库添加货物
public void addToWarehouse(T good){
Thread t = Thread.currentThread();
BlockingQueue<T> goods = warehouse.getGoods();
while(true){
try {
boolean offer = goods.offer(good,100,TimeUnit.MILLISECONDS);
if(offer){
break;
}else {
System.out.println(t.getName()+ " 等待。。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(t.getName()+" 向仓库存放货物:"+goods+",总量:"+warehouse.getGoods().size());
}
//常量没有setter
public Warehouse<T> getWarehouse() {
return warehouse;
}
}
//消费者
public class Consumer {
public <T> T getFromWarehouse(Warehouse<T> warehouse){
Thread t = Thread.currentThread();
BlockingQueue<T> goods = warehouse.getGoods();
T element;
try {
while(true){
element = goods.poll(100, TimeUnit.MILLISECONDS);
if(element==null){
System.out.println(t.getName()+" 等待。。。");
}else {
break;
}
}
System.out.println(t.getName()+" 取到一个元素:"+element.toString());
return element;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
//仓库类
public class Warehouse<T> {
private BlockingQueue<T> goods;
public Warehouse(){
this.goods = new ArrayBlockingQueue<>(10);
}
public BlockingQueue<T> getGoods() {
return goods;
}
public void setGoods(BlockingQueue<T> goods) {
this.goods = goods;
}
@Override
public String toString() {
return "Warehouse{" +
"goods=" + goods.toString() +
'}';
}
}