什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻
塞队列来实现生产者
-
消费者模型?
阻塞队列(
BlockingQueue
)是一个支持两个附加操作的队列。
这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元
素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿
元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
JDK7
提供了
7
个阻塞队列。分别是:
ArrayBlockingQueue
:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue
:一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue
:一个支持优先级排序的无界阻塞队列。
DelayQueue
:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue
:一个不存储元素的阻塞队列。
LinkedTransferQueue
:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque
:一个由链表结构组成的双向阻塞队列。
Java 5
之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实
现生产者,消费者模式,主要的技术就是用好,
wait ,notify,notifyAll,sychronized
这些关键字。而
在
java 5
之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,
安全方面也有保障。
BlockingQueue
接口是
Queue
的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工
具,因此他具有一个很明显的特性,当生产者线程试图向
BlockingQueue
放入元素时,如果队列已
满,则线程被阻塞,当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞,
正是因为它所具有这个特性,所以在程序中多个线程交替向
BlockingQueue
中放入元素,取出元
素,它可以很好的控制线程之间的通信。
阻塞队列使用最经典的场景就是
socket
客户端数据的读取和解析,读取数据的线程不断将数据放入
队列,然后解析线程不断从队列取数据解析。
乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候
都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做
操作之前先上锁。再比如
Java
里面的同步原语
synchronized
关键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是
在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于
write_condition
机
制,其实都是提供的乐观锁。
在
Java
中
java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式
CAS
实
现的。
乐观锁的实现方式:
1
、使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可
以采取丢弃和再次尝试的策略。
2
、
java
中的
Compare and Swap
即
CAS
,当多个线程尝试使用
CAS
同时更新同一个变量时,只有其
中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争
中失败,并可以再次尝试。
CAS
操作中包含三个操作数
——
需要读写的内存位置(
V
)、进行比
较的预期原值(
A
)和拟写入的新值
(B)
。如果内存位置
V
的值与预期原值
A
相匹配,那么处理器会自
动将该位置值更新为新值
B
。否则处理器不做任何操作。
CAS
缺点:
1.
ABA
问题:
比如说一个线程
one
从内存位置
V
中取出
A
,这时候另一个线程
two
也从内存中取出
A
,并且
two
进行了一些操作变成了
B
,然后
two
又将
V
位置的数据变成
A
,这时候线程
one
进行
CAS
操作发现内存中仍然是
A
,然后
one
操作成功。尽管线程
one
的
CAS
操作成功,但可能存在
潜藏的问题。从
Java1.5
开始
JDK
的
atomic
包里提供了一个类
AtomicStampedReference
来解决
ABA
问题。
2.
循环时间长开销大:
对于资源竞争严重(线程冲突严重)的情况,
CAS
自旋的概率会比较大,
从而浪费更多的
CPU
资源,效率低于
synchronized
。
3.
只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环
CAS
的
方式来保证原子操作,但是对多个共享变量操作时,循环
CAS
就无法保证操作的原子性,这个
时候就可以用锁。
什么是
Daemon
线程?它有什么意义?
所谓后台
(daemon)
线程,也叫守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,
并且这个线程并不属于程序中不可或缺的部分。
因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来
说, 只要有任何非后台线程还在运行,程序就不会终止。
必须在线程启动之前调用
setDaemon()
方法,才能把它设置为后台线程。注意:后台进程在不执行
finally
子句的情况下就会终止其
run()
方法。
比如:
JVM
的垃圾回收线程就是
Daemon
线程,
Finalizer
也是守护线程。