创建、启动、控制、多线程同步、线程池
进程和线程
进程:是处于运行过程的程序,有一定的独立功能,是系统进行资源分配和调度的一个独立的单位。特征:独立性,动态性,并发行。
线程:是进程的执行单元,是独立运行的。线程的调度管理由进程本身负责完成。
线程
- 创建和启动
1.1 继承Thread类创建和启动
Java中Thread类代表线程
,线程对象都必须是Thread类或其子类的实例。
步骤:定义Thread类的子类,重写run()方法;run()方法中的代码就是线程要完成的任务,即线程执行体
; 创建Thread子类的实例; 调用线程对象的start()方法来启动线程
。
多个线程之间无法共享线程类的实例变量。
1.2. 实现Runnable接口
是函数式接口。
步骤:定义Runnable接口的实现类,重写的run()方法作为线程执行体
;创建Runnable实现类的实例作为Thread的target来创建Thread对象为真正的线程对象;调线程对象的start()方法启动线程。
可以共享线程类的实例变量。
1.3 使用Callable和Future
通过Callable接口和Future接口就可以在任务执行结束后获得任务执行的结果。
Callable是函数式接口,call()方法作为线程执行体
。call()方法有返回值,call()方法可以声明抛出异常。Callable接口不是Runnable接口的子接口,Callable对象不能直接作为Thread的target。
Future接口代表Callable接口里call()方法的返回值。FutureTask为Future接口和Runnable接口的实现类,FutureTask实现类可以作为Thread类的target
。
步骤:实现Callable接口的call()方法并创建Callable接口的对象;使用FutureTask类包装Callable对象;创建FutureTask的对象作为Thread对象的target。
Future接口的get()方法:会导致程序阻塞,子线程结束后才会得到返回值。
- 线程的生命周期
共有5种状态。
新建:用new关键字创建了一个线程后,线程就处于新建状态
。JVM会分配空间初始化成员变量的值,但不会表现出任何线程的动态特征,也不会执行线程的线程执行体。
就绪:调用start()方法后就处于就绪状态
,JVM会创建方法调用栈和程序计数器。
运行:处于就绪状态的线程获得cpu后处于运行状态。
- 就绪和运行状态的转换不受程序的控制,由系统线程调度决定。
yield()方法会让运行状态的线程转入就绪状态
。
阻塞:调用了sleep()方法;调用了阻塞式IO方法;在等某个通知(notify);调用了suspend()方法将线程挂起(容易导致死锁)。
- 阻塞后会重新进入就绪状态。
调用resume()方法恢复被挂起的线程。
死亡:run()/call()方法执行结束,线程正常结束;抛出一个未捕获的Exception/Error;stop()方法结束该线程(容易导致死锁)
- 主线程结束后其他线程不受影响,并不会随之结束。
判断线程是否已经死亡:isAlive()方法。线程处于就绪、运行、阻塞时返回true,处于新建、死亡时返回false。
- 控制线程
控制线程的执行。
3.1 join线程
A线程中调用了B线程的join()方法,A线程会等待B线程完成才会执行。
3.2 后台线程
在后台运行,为其他的线程提供服务。别名:守护线程,精灵线程。
所有的前台线程都死亡,后台线程会自动死亡
。
使用Thread对象的setDaemon(true)
方法将指定的线程设置为后台线程。
setDaemon(true)方法必须在start()方法之前调用。
3.3 线程睡眠:sleep
当前执行的线程暂停一段时间,进入阻塞状态。
调用Thread类的静态sleep()方法实现。
线程调用sleep()方法后,在该线程的睡眠时间内,即使系统种没有其他的可以执行的线程,该线程也不会执行。
3.4 线程让步:yield
是Thread类的静态方法,会让执行的线程暂停进入就绪状态。
会让当前线程暂停,系统的线程调度器重新调度一次。
优先级与当前线程相同,或者优先级高于当前线程的处于就绪状态的线程会获得执行的机会。
3.5 改变线程的优先级
优先级高的线程获得多的执行机会,优先级低的线程获得少的执行机会。
- 线程同步
4.1 线程安全问题
使用同步监视器来解决,即通过使用同步代码块。
4.2 使用synchronized进行同步
- 同步代码块
线程开始执行同步代码块之前要先获得对同步监视器的锁定。任何时刻只有一个线程可以获得对同步监视器的锁定,在执行完同步代码块之后,线程就会释放对同步监视器的锁定。
可能被并发访问的共享资源的对象做同步监视器,同步监视器控制着对象的synchronized代码
。
监视器作用:确保同一时间只有一个线程可以访问共享资源。
- Java中监视器的实现
JVM中,每个对象关联监视器,即每个对象都是一个临界区
。实现监视器的互斥功能:每个对象关联着一个锁,即操作系统中的信号量。互斥是一个二进制的信号量。Java内置锁也称为互斥锁,即锁实际上是一种互斥机制
。
信号量:分为二进制信号量(互斥锁)和计数信号量。用来解决临界区及进程同步问题。存在P(wait())操作与V(signal)操作两种操作。
同步监视器和锁:这两者的关系类似于JVM中方法区和永久代的关系。
- 同步方法
就是使用synchronized关键字修饰某个方法。
修饰实例方法:不需要显式指定同步监视器,同步监视器是this,即调用该方法的对象。
并不能使调用该方法的多个对象在执行顺序上互斥
。
敲重点
:synchronized修饰this代码块以及非static的方法时,即当锁为对象锁
时,只能防止多个线程同时执行同一个对象的同步代码段
。
线程安全的类:Vector,Hashtable,StringBuffer都是线程安全的类。该类的对象可以被多个线程安全的访问,每个线程调用该对象的任何方法
之后都会得到正确结果。只对线程安全类的会改变竞争资源的方法进行同步。
- 同步静态方法
就是使用synchronized修饰静态方法,控制该类的所有实例的并发访问
,限制多线程中该类的所有实例同时访问该类所对应的代码块。
修饰静态方法:同步监视器就是当前类的class对象锁。class对象锁可以控制对静态成员的并发操作。
class对象锁相当于该类的一个全局锁,无论实例多少个对象,线程都共享该锁
。
4.3 释放同步监视器的锁定
无法显式释放对同步监视器的锁定。
会释放:在同步方法或者同步代码块中出现未处理的Error或者Exception导致异常结束时,会释放。执行了同步监视器的wait()方法后,当前线程暂停并释放同步监视器
。
不会释放:调用sleep()、yield()方法时;
synchronized的缺陷:当获取锁的线程因为io等原因阻塞而不能释放锁,其他的线程只能一直等待,影响程序效率。synchronized不可以知道线程有没有成功获取到锁。
4.4 同步锁
Lock同步锁可以让等待锁的线程响应中断,但synchronized会让等待的线程一直等待下去,不能够响应中断
。
通过同步锁可以不让等待的线程一直无限期的等待下去;通过同步锁可以知道线程有没有成功获取到锁。
通过显式定义同步锁对象--> Lock对象
实现同步。
通过同步锁实现同步时,必须要用户手动去释放锁,如果没有手动释放,会导致死锁。
使用Lock时显式使用Lock对象作为同步锁,使用synchronized时隐式使用当前对象作为同步监视器。
Lock
Lock是一个接口,lock(),tryLock():用来获取锁
,unlock()
:用来释放锁。
lock()方法:锁已经被其他线程获取,则进行等待。
在try{}catch{}块中使用Lock,释放锁的操作在finally中进行
,保证锁一定会释放,防止死锁的发生。
以这种形式使用:
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
tryLock()方法:在获取到锁时返回true,获取不到时返回false,这个方法在拿不到锁时不会一直在等待会立即返回。
tryLock()获取锁:
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
lockInterruptibly()方法:使用这个方法获得锁时,线程在等待获取锁时,能够响应中断
,可以中断线程的等待状态
。
要在try块中或者调用lockInterruptibly()的方法外抛出InterruptedException异常。
lockInterruptibly()方法可以中断线程的等待状态的原理类似于线程调用wait()后进行中断会重新唤醒线程的原理。
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
ReentrantLock
可重入锁
,是唯一一个实现了Lock接口的类。
独占锁
:同一个时间点只能被一个线程访问。
轮询锁和定时锁
:通过tryLock()方法实现的,tryLock()方法在获取锁时如果锁已经被其他线程所占有,则立即返回,不会一直等待阻塞下去,同时在返回后会释放自己所持有的锁,可以根据返回的结果进行重试或者取消,避免死锁发生。
公平锁
:按照请求锁的顺序来获取锁。
非公平锁的性能要高于公平锁。
可中断锁
:使用lockInterruptibly()方法可以中断线程的等待状态。
ReentrantLock和synchronized都是可重入锁。
可重入性:表明了锁的分配机制,是基于线程的分配,不是基于方法调用的分配。
ReadWriteLock
是一个接口,只有两个方法,readLock():用来获取读锁,writeLock():用来获取写锁。将文件的读与写操作分开,分成两个锁分配给线程。使得多个线程的读操作不冲突。是一个互斥锁:读锁和写锁相互互斥,但是多个读锁之间不互斥
。
具有重入性,读线程插队,写锁降级到读锁,读锁升级到写锁,释放优先。
ReentrantReadWriteLock
实习了ReadWriteLock接口。
4.5 死锁
一个线程等待另一个线程持有的锁时,另一个线程也在等待该线程所持有的锁时,两个线程都会处于阻塞状态,会出现死锁。即两个线程相互等待对方释放同步监视器时会发生死锁
。
线程通信
- 使用wait()、notify()、notifyAll()进行通信
与对象监视器配合完成线程间通信。用到了两种机制:互斥锁机制和内置的条件变量机制。
synchronized修饰的方法中同步监视器是this,可以直接调用这三个方法。
synchronized修饰的同步代码块中必须使用synchronized括号里面的对象来调用这三个方法。
wait()、notify()、notifyAll()是Object类的final方法,无法被重写。调用这三个方法时当前线程必须获得对调用这三个方法的对象的同步监视器的锁定。
wait()
:使当前线程阻塞,直到接到通知或者当前线程被中断为止。
调用wait()方法的当前线程会释放对同步监视器的锁定,让出cpu,进入等待状态,线程的生命周期状态被调整为WAITTING。
直到其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程,该线程的生命周期状态会变为RUNNABLE。
被唤醒的处于RUNNABLE状态的线程会与其他处于RUNNABLE状态的线程竞争锁,如果被唤醒的线程竞争到锁,该线程会从wait()方法处返回,然后继续往下执行。
notify()
:唤醒在此同步监视器上等待的单个线程
。
只是唤醒沉睡的线程,直到执行完synchronized代码块或是遇到wait()方法,才会释放锁。
只有当前线程放弃对同步监视器的锁定后,才会执行被唤醒的线程
。
在这个同步监视器上等待的线程很多的情况下会选择一个任意的线程去唤醒。
notifyAll()
:唤醒在此同步监视器上的所有的等待的线程。
wait(long)和wait(long,int)
:对前一个方法,如果等待线程在接到通知或被中断之前,已经超过了wait(long)中规定的时间,该等待线程就会自动释放自己被唤醒,唤醒后回去竞争锁。
wait(long)和wait(long,int)返回时不会返回任何相关的信息,所以不能确定是因为接到了通知还是因为超时而被唤醒。可以通过设置标志位来进行判断。
锁池
和等待池
锁池
(入口集):一个对象锁已经被一个线程所拥有,其他线程想要得到该对象锁时,就会进入该对象的锁池中。
进入锁池的线程的生命周期状态被调整为BLOCKED。
等待池
(等待集):一个线程调用了它所拥有的对象锁的对象的wait()方法,这个线程会进入阻塞,并释放该对象锁,进入该对象的等待池。
进入等待池的线程的生命周期状态被调整为WAITTING。
等待池中的线程不会去竞争对象的锁,即不会参与线程调度。
敲重点
:notify()和notifyAll()的区别
notifyAll()方法:调用后,将全部的线程由等待池移到锁池,参与锁的竞争,竞争到锁的线程会继续执行,没有竞争到锁的线程留在锁池等待锁被释放后再次参与竞争。
notify()方法:会将等待池中的一个随机线程移到锁池。
wait()、notify()、notifyAll()方法的底层原理
wait()
:调用wait()时,会将synchroniezd锁定的锁释放掉,再将当前的线程阻塞再一个内置的条件上
,这个内置的条件只能被notify()/notifyAll()改变后该当前线程才可以重新去竞争锁。
notify()/notifyAll()
:起到一个内置条件变量的作用。调用notify()/notifyAll()后,处于wait()状态中的线程等待的内置条件会被满足。
wait()线程所等待的内置条件被满足后,wait()的线程还要获得锁,它会等notify()/notifyAll()的线程执行完同步代码块并将锁释放掉后,去竞争被释放的锁。
总结:一个线程使用wait()后可以被唤醒的情况
a. 其他线程调用Object.notify(),并且当前线程T正好是被选中唤醒的。
b. 其他线程调用Object.notifyAll()。
c. 其他线程中断T。
d. 指定的等待时间超时。
- 使用Condition进行线程通信
Condition是个接口,Condition与Lock配合完成线程间通信。
通过lock.new Condition()来创建Condition对象,实际上new出的是ConditionObject对象。ConditionObiect类是AQS的一个内部类,是Condition在Java并发中的具体实现。
(ConditionObject和AQS?)
Condition与synchronized在功能特性上的不同点
Condition可以支持不响应中断,而Object的方式不支持。?wait()中断
Condition可以支持多个等待队列(new多个Condition对象),Object的方式只支持一个。
Condition可以支持超时时间的设置,Object不支持。
Condition中支持的方法
类似于Object,Condition中有:
void await() throws InterruptedException
:使当前线程阻塞。
当其他线程调用Condition的signal()或signalAll()方法或中断当前线程时会唤醒当前线程,并且当前线程获取到Lock锁时会从await()方法处返回。
当前线程在等待状态中被中断则会抛出异常。
long await(long)
:使当前线程阻塞。
直到当前线程接到通知,中断或超时时会退出阻塞状态。
boolean await(Date) throws InterruptedException
:类似于上一个。
void awaitUninterruptibly()
:使当前线程阻塞,直到被通知,对中断不做响应
。
void signal()
:唤醒在Condition对象上等待的线程。
会将该线程从等待队列移到同步队列(从等待池移到锁池),在同步队列中可以竞争到Lock锁,则会从等待方法处返回。
void signalAll()
:唤醒所有的在Condition对象上等待的线程。
- Condition的await()和signal()方法必须在lock.lock()和lock.unlock()方法之间才可以使用。
Condition实现原理分析(ConditionObject的具体实现)
以ConditionObject的等待队列、等待、通知三方面分析ConditionObject的具体实现。
- 等待队列
ConditionObject的等待队列是一个单向的FIFO队列,队列上的每个节点都是等待在Condition对象上的线程的引用。
调用Condition.await()方法的线程会释放锁,构造相应的Node节点进入Condition内部维护的等待队列中等待,线程的状态变为WAITING。节点的定义复用AQS的Node定义。
ConditionObject的等待队列的相关定义和方法
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
......
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将当前线程包装成Node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//尾插入
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
//更新latWaiter
lastWaiter = node;
return node;
}
......
}
等待队列的基本结构如下图:
ConditionObject包含等待队列的首节点firstWaiter和尾节点lastWaiter,通过等待队列的头尾指针来管理等待队列。
线程调用await()方法时,会调用addConditionWaiter()方法将线程加入等待队列中。
插入新节点采用尾插法,将原有节点的nextWaiter指向新节点,并更新尾节点lastWaiter指向新插入的新节点。
更新节点并没有像AQS更新同步队列使用CAS是因为调用await()方法的线程一定是获得了锁的线程,锁保证了操作线程的线程安全
。
一个Lock同步锁可以有多个等待队列 --> 多次调用lock.new Condition()方法创建多个Condition对象。
对象的对象监视器上只能有一个同步队列和一个等待队列
。
并发包中的Lock有一个同步队列和多个等待队列。
等待队列不带头节点,同步队列带头节点(AQS?)
- 等待过程
Condition的await()系列的方法会使当前获取loak锁的线程进入到等待队列,当前线程可以从await()方法返回的话就一定会获得相关的lock锁。
在await()代码过程主要分析3点:怎么将当前线程加入到等待队列中去的?释放锁的过程?怎么从await()方法退出返回?
ConditionObject的await()方法
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程包装成Node,尾插入等待队列
Node node = addConditionWaiter();
//释放当前线程所占用的锁 ,释放过程中会唤醒同步队列中的下一个节点
long savedState = fullyRelease(node);
int interruptMode = 0;
//当前线程受到唤醒加入到同步队列时退出while循环
while (!isOnSyncQueue(node)) {
//当前线程进入等待状态
LockSupport.park(this);
// 当前线程受到中断时退出while循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//自旋等待获取到lock锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//处理中断的情况
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
具体过程如下:
a. 调用addConditionWaiter()方法将当前线程加入到等待队列。
b. 在fullyRelease()方法中进行同步锁的释放。
在fullyRelease()方法中调用release()方法释放同步锁并唤醒在等待队列中的头节点的后继节点引用的线程
。成功释放就正常返回,失败则会抛出异常。
c. 在while()循环中线程进入等待状态,在线程被中断或线程在被唤醒后被移到同步队列时退出while循环,接着不断自旋试着去获取到同步锁。
获取到同步锁时,线程会从await()方法返回。
不响应中断
不相应中断调用了Condition的awaitUninterruptibly()方法,在这个方法中减少了对中断的处理,也省略了抛出被中断的异常。
- 通知过程
Condition的signal()方法会将等待队列中的等待时间最长节点即等待队列的头节点移动到同步队列,使该节点可以去竞争锁。
ConditionObject的signal()方法
public final void signal() {
//先检测当前线程是否已经获取到lock锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取等待队列的第一个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
之后会将等待队列的头节点从等待队列中移除,再将头节点插入到同步队列中,最后使用LockSypport.unpark()方法唤醒该节点的线程。
signalAll()
会调用doSignalAll()方法将等待队列的每一个节点都移入到同步队列中。
- 使用阻塞队列(BlockingQueue)
是一个接口。
使用阻塞队列的优点:不需要我们自己去处理什么时候阻塞线程和唤醒线程,阻塞队列会帮我们去处理。
阻塞队列有一个完整队列所具有的基本功能。在多线程环境下,阻塞队列自动管理了多线程间的自动等待和唤醒功能
。
多线程中,通过队列实现数据共享。通过线程安全的队列类,解决了多线程中的高效安全传输数据
的问题。
阻塞队列中线程的阻塞:在某些情况下会阻塞,条件满足后被阻塞的线程会被自动唤醒。
常见阻塞场景:1. 在队列为空时,消费者线程会被阻塞,直到该阻塞队列为非空。2. 在队列满时,生产者线程会被阻塞,直到该阻塞队列变为非满。
BlockingQueue阻塞队列的方法
分为两大类方法:放入数据和获取数据。
- 放入数据
offer(Object)
:将数据加入到阻塞队列,可以放入返回true,否则返回false。该方法不阻塞当前执行方法的线程
。
offer(Object,long)
:在等待时间内不能往等待队列中加入数据就返回false。
put(Object)
:如果阻塞队列没有空间加入数据,则调用此方法的线程被阻塞直到阻塞队列里面有空间再继续加入
。
- 获取数据
poll(time)
:取走阻塞队列中的排在首位的对象。不能立即取出时会等待time参数规定的时间,超过time时间后还取不到会返回null。
take()
:取走阻塞队列里排在首位的数据。在阻塞队列为null时,会使当前线程阻塞直到阻塞队列有新的数据被加入
。
drain()
:可以指定获取数据的个数的一次性
从阻塞队列中获取所有可用的数据对象。不需要分批次的加锁或释放锁
。
常见的阻塞队列
- ArrayBlockingQueue
基于数组的阻塞队列的实现。
在其内部维护了一个定长数组,来缓存阻塞队列中的数据对象。
按照FIFO先进先出排序元素。
内部的两个整型变量分别标识着队列头部和尾部在数组中的位置。
ArrayBlockingQueue的生产者和消费者线程公用一个锁对象
,所以生产者和消费者线程无法并行运行。
创建ArrayBlockingQueue时,可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
- LinkedBlockingQueue
基于链表的阻塞队列。
按照FIFO先进先出排序元素。
在其内部维护了一个由链表构成的数据缓冲队列,来缓存阻塞队列中的数据对象。
可以通过LinkedBlockingQueue的构造函数来指定阻塞队列的最大缓存容量。
在构造LinkedBlockingQueue对象时,没有指定容量大小的时会默认使用一个类似无限大小的容量(Integer.MAX_VALUE)。这样可能会产生当生产者线程生产的速度大于消费者线程消费的速度时会使得系统内存消耗严重,可能会造成内存泄漏等问题。
每个添加到LinkedBlockingQueue队列中的数据都会被封装成Node节点
。
生产者线程和消费者线程分别采用了独立的锁来控制数据同步
,生产者和消费者可以并行的操作阻塞队列的数据。
生产者线程往阻塞队列中放入数据时,阻塞队列行将数据缓存在内部,生产者线程会立即返回。
当阻塞队列达到最大的缓存容量时,会阻塞生产者线程
,消费者线程消费掉阻塞队列中的一份数据时生产者线程会被唤醒。
消费者线程的原理类似于生产者线程。
在插入或删除元素时和ArrayBlockingQueue的不同之处
ArrayBlockingQueue在插入或删除时不会产生或销毁任何额外的对象实例
。
LinkedBlockingQueue在插入或删除时会生成一个额外的Node对象
。
深入ArrayBlockingQueue和LinkedBlockingQueue的异同
相同
当队列为空时消费者线程被阻塞;当队列为满时,生产者线程被阻塞。都是可阻塞的队列。
都实现了BlockingQueue接口。内部都是使用了ReentrantLock和Condition来保证生产和消费的同步。
使用Condition的await()和signal()方法来进行线程通信。
不同
a. 锁机制不同
LinkedBlockingQueue中锁是分离的,生产者线程和消费者线程使用的不同的锁。
ArrayBlockingQueue中生产者和消费者线程使用的是同一把锁。
b. 两者的底层实现机制不同
LinkedBlockingQueue内部维护一个链表结构。在插入或删除时会生成一个额外的Node对象,可能在长时间高并发处理大量数据时会对GC产生较大的影响。
ArrayBlockingQueue内部维护了一个循环队列数组。在插入或删除元素时不会产生或销毁任何额外的对象实例。
c. 构造时候的区别
LinkedBlockingQueue有默认的容量大小,也可以传入指定的容量大小。
ArrayBlockingQueue在初始化时必须指定大小。
- DelayQueue
是一个没有大小限制的阻塞队列,所以进行插入操作的生产者线程永远不会被阻塞,获取数据的消费者线程会被阻塞。
这个阻塞队列中的元素只有当其指定的延迟时间到了,才可以从队列中获取到该元素
。
使用场景:用该阻塞队列来管理一个超时未响应的连接队列。
- PriorityBlockingQueue
基于优先级的阻塞队列。
通过在构造函数中传入Compator对象来决定优先级的判断。内部控制线程采用的锁是公平锁
。
该阻塞队列不会阻塞生产者线程,在没有可以取出的数据时会阻塞消费者线程。
使用时注意不要让生产者线程生产的速度大于消费者线程消费的速度,否则会产生严重消耗堆内存。
- SynchronousQueue
是一种无缓冲的等待队列。
生产者消费者模型
在系统中存在生产者和消费者,他们通过内存缓冲区进行通信。
实现:生产者是一些线程,消费者是一些线程,内存缓冲区使用List数组队列,然后处理多线程之间的协作。
注意:内存缓冲区为空时消费者必须等待;内存缓冲区为满时生产者必须等待。
synchronized锁住的是括号里的对象,不是代码。?
同步监视器为类锁
时,实现了全局锁的效果,只要这个synchronized括号中的对象为该类的对象时,都会实现同步锁定的效果。synchronized锁住的是类的Class对象。
在同步方法或者同步代码块中出现未处理的Error或者Exception?
执行了同步监视器的wait()方法后,当前线程暂停并释放同步监视器?
同步监视器
同步监视器和锁
同步监视器和synchronized
synchronized锁的是什么?
synchronized的实现原理?
synchronized修饰实例方法时
的同步监视器?
线程安全的类?
volatile
同步方法和同步代码块
Lock和异常
Lock锁住的对象?
可重入锁
wait(),notify(),notifyAll()和生产者消费者
怎么中断
wait(long timeout)超时之后线程处于锁池还是等待池。
wait()和中断
为什么wait()后再进行中断线程就会活过来?
AQS
AQS更新同步队列使用CAS?
等待队列不带头节点,同步队列带头节点(AQS?)
AQS的模板方法release方法释放AQS的同步状态并且唤醒在同步队列中头结点的后继节点引用的线程?
在await()中释放锁的过程?
Condition的同步队列
Condition的同步队列和Object的同步队列
怎么实现无锁同步?
Concurrent包原理
Concurrent原理
juc包
park和unpark()?
transient
双重检查上锁实现的单例模式。
Java单例模式中双重检查锁的问题
https://www.cnblogs.com/wanly3643/p/3992186.html
https://blog.csdn.net/qpc908694753/article/details/61413693
https://hacpai.com/article/1488015279637
https://blog.csdn.net/qq_38663729/article/details/78232648
看了异常后:Lock的interruptLock()的抛出异常
使用wait()后被中断唤醒抛出异常
Condition的不响应中断和响应中断 抛出的异常