0%

阻塞队列是指一个支持两个附加操作的队列,这两个附加操作支持阻塞地插入和移除的方法。
• 支持阻塞地插入方法,队列满时,队列会阻塞插入元素的线程,直到队列有空位;
• 支持阻塞地移除方法,当队列为空时,队列会阻塞移除元素的线程,直到队列有元素。

阻塞队列一般用于生产-消费的场景,插入和移除操作的4种处理方法如下:

java-concurrence-queue

• 抛出异常:当队列满/空时,再往队列添加/移除元素,就会抛出异常;
• 返回特殊值:当队列插入元素时,会返回插入是否成功的布尔值,当移除元素时,则从队列取回一个元素,不存在返回NULL。
• 一直阻塞:当队列满时,再往队列put元素,队列会一直阻塞生产线程直到队列可用或响应中断退出;当队列为空时,如果从队列take出一个元素,则会一直阻塞消费线程直到队列不为空。
• 超时退出:当队列满/空时,如果添加/移除一个元素,则会线程阻塞到指定的时间后退出。

JDK6一共提供了6种阻塞队列,以应对各种场景的生产-消息模型,以下是这6种阻塞队列的类关系图:

阅读全文 »

ReentrantLock是排他锁,也就是同一时间也能有一个线程进行访问,而读写锁可以同一时间有多个读线程进行访问,而写线程访问时,所有读线程和其它写线程会被阻塞。读写锁维护了一对锁,一个读锁一个写锁,通过分离读写,使并发能力得到很大提高。
需要注意的是ReentrantReadWriteLock实现的是ReadWriteLock接口而不是Lock接口。

一、ReentrantReadWriteLock的特征

​ • 公平性:支持非公平(默认)和公平的获取锁方式,吞吐量还是非公平锁优于公平锁;
​ • 重进入:该锁支持重进入。读线程获取读锁后,能够再次获取读锁;写线程在获取写锁后能再次获取写锁,同时也能获取读锁;
​ • 锁降级:遵循获取写锁、获取读锁再释放写锁的顺序,写锁能降级成为读锁。
​ • 锁中断:读写锁都支持获取锁期时被中断;
​ • 重入数:读锁和写锁的重入次数都为65535.

二、ReentrantReadWriteLock的实现

1、读写的状态(state变量)设计
读写锁的状态同样是依赖于内部同步器的状态,也就是int型的state变量,读写锁的实现是把state变量切割为两个部分,高16位代表读,低16位代表写。如下图:

阅读全文 »

ReentrantLock重入锁,是指对于同一个线程,在再次获取锁时,不会阻塞,是一种优化锁。Synchronized关键字是支持隐式重入的同步,对于同一个线程,可以支持多次进入而不阻塞。
ReentrantLock支持可重入的实现主要是通过AQS中的volatile共享变量state的计数来实现的,对于同一个线程,只会进行计数,不会阻塞,相反,在释放同步状态时,需要释放所有计数,才会释放锁。 ReentrantLock支持公平锁和非公会锁,公平锁是指严格按照FIFO的规则,先阻塞的线程先获取锁,后阻塞的线程后获取锁。ReentrantLock通过内部实现的FairSycn和NonFairSync两个内部类的tryAcquire方法实现的不同来实现公平锁和非公会锁。
首先看ReentrantLock对AbstractQueuedSynchronizer的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static abstract class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;

/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
* 由Lock接口的Lock.lock执行,主要原因是由子类来快速实现非公平锁版本,
*
*/
abstract void lock();

/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
//非公平锁的tryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步计数
int c = getState();
if (c == 0) {
//如果计数为初始状态,马上设置state后返回,并把当前线程设置到独占者线程中
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程为前面的独占者的线程,将把计数+acquires后,设置回state变量
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;
}

//释放同步状态,公平和非公会锁都使用此方法释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//当前state为0时,说明释放成功,否则继续state变量-1
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
….

}

非公平锁版本的同步器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final static class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//CAS同步状态变量成功即快速上锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
//使用非公平锁版本的nonfairTryAcquire
return nonfairTryAcquire(acquires);
}

}

公平锁的同步器实现:

阅读全文 »

AQS主要是通过同步队列,独占式同步状态获取、释放和共享式同步状态获取、释放及超时状态获取同步等核心数据结构和模板方法。

一、同步队列(CLH lock queue)的结构

​ 主要由Node内部类来实现构成同步队列的节点,Node本身维护了几种状态:
​ • CANCELLED : 代表线程已经取消
​ • SIGNAL:代表后继节点需要唤醒
​ • CONDITION:代表线程在condition queue中等待某一条件
​ • PROPAGATE:代表后继节点会传播唤醒的操作,在共享模式中使用
​ • INITIAL:初始状态
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 代表线程已经取消*/
static final int CANCELLED = 1;
/** 代表后继节点需要唤醒 */
static final int SIGNAL = -1;
/** 代表线程在condition queue中等待某一条件 */
static final int CONDITION = -2;
/**代表后继节点会传播唤醒的操作,在共享模式中使用*/
static final int PROPAGATE = -3;
//等待状态,上面的几种状态
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//获取同步状态的线程
volatile Thread thread;
//等待队列中的后继节点
Node nextWaiter;

同步队列的结构如下图:

阅读全文 »

AQS是AbstractQueuedSynchronizer类的简称,Java并发工具包的大部分类的实现基础都基于这个类。AQS的核心是通过一个共享变量来同步状态,AQS只负责:
• 线程阻塞队列的维护
• 线程阻塞和唤醒
共享变量的修改都是通过Unsafe类提供的CAS操作来实现的,AQS的主要方法是acquire和release,是个模板方法类。
并发工具包中AQS的类关系图

一、AQS的简单实现

下面通过一个demo来看怎么使用AQS和Lock接口来实现一个互斥锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
this.setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
if(getState() == 0) throw new IllegalMonitorStateException();
this.setExclusiveOwnerThread(null);
this.setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}


Condition newCondition(){
return new ConditionObject();
}
}

private final Sync sync = new Sync();

@Override
public void lock() {
sync.acquire(1);
}

@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(0);
}

public boolean isLocked(){
return sync.isHeldExclusively();
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
public static void main(String[] args) {
final Mutex lock = new Mutex();
for(int i = 0; i < 10; i++){
new Thread(new Runnable(){
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getId() + " acquired the lock.");
lock.unlock();
}
}).start();
//简单地让线程让for循环的顺序阻塞在lock上
SleepUtils.second(10);
}
}

}

通过一个内部类来继承AbstractQueuedSynchronizer类,而Lock接口中的方法主要靠Sync内部类来实现。

阅读全文 »

JMM 和happens-before的关系图:

jmm7

一、定义:

指两个操作之间的执行顺序,这两个操作可以在同一线程内,也可以在不同的线程之间。因此,JMM可以通过happens-before关系 来向程序员提供跨线程的内存可见性保证。

JMM线程规范中对happens-before的关系定义如下:
1)、如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见。而且第一个操作的执行顺序排在第二个之前;
2)、两个操作之间存在happens-before关系,并不意味着Java平台必须按照happens-before关系指定的顺序来执行,如果重排序后的执行结果和与按照happens-before关系指定的执行顺序一致,那么这种重排序未非法。

阅读全文 »

一、Java内存模型的基础

1、Java并发编程是利用共享内存的方式进行线程之间的隐式通信,在这种模型里面,同步是显式的,程序必须指定某个方式或代码块在线程间的执行是以互斥的方式进行的。

2、Java内存模型的抽象结构
Java线程之间的通讯是通过JMM模型来控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。JMM定义了主存和线程之间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有自己的本地内存拷贝,本地内存存储了该线程读/写共享变量的副本,本地内存是一个抽象概念,实际上是不存在的。它包含了缓存,写缓冲区,寄存器及其它的硬件和编译器优化。

jmm1

3、重排序
执行程序时,为了优化性能,一般都会进行重排序。重排序分为以下3个类型:
1).编译器优化的重排序,在不改变单线程语义的前提下,可以重新安排语句的执行顺序。
2).指令级并行的重排序,现代处理器采用指令并行技术来进行多条指令重叠执行。如果不存在 数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
3).内存系统的重排序,由于处理器使用缓存和读/写缓冲区,使得加载和存储操作看起来是乱序执行。
Java代码从编译到执行,会经历以下的重排序:

阅读全文 »

常见垃圾回收器算法:

1、引用计数法(Reference counting):
为每个对象配置一个整型的计数器,对象被引用时+1,引用失效时-1。存在循环引用问题,JAVA没使用;

2、标记-清除算法(Mark-Sweep):
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段首先通过根节点,标记所有从根节点开始的较大对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。该算法最大的问题是存在大量的空间碎片,因为回收后的空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的工作效率要低于连续的空间。

jvm9

3、复制算法(Coping):
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大。因此在真正需要垃圾回收的时刻,复制算法的效率是很高的。又由于对象在垃圾回收过程中统一被复制到新的内存空间中,因此,可确保回收后的内存空间是没有碎片的。该算法的缺点是将系统内存折半。
Java新生代的串行垃圾回收器中使用了复制算法 。新生代分为Eden区,From区和To区。其中 from 空间和 to 空间可以视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。
在垃圾回收时,eden 空间中的存活对象会被复制到未使用的 survivor 空间中 (假设是 to),正在使用的 survivor 空间 (假设是 from) 中的年轻对象也会被复制到 to 空间中 (大对象,或者老年对象会直接进入老年代,如果 to 空间已满,则对象也会直接进入老年代)。此时,eden 空间和 from 空间中的剩余对象就是垃圾对象,可以直接清空,to 空间则存放此次回收后的存活对象。这种改进的复制算法既保证了空间的连续性,又避免了大量的内存空间浪费。

阅读全文 »

垃圾回收一般指回收方法区和堆区的内存

一、判断对象已死

1、引用计数法?

2、可达性分析算法!
GC Roots作为对象起点,从节点开始搜索,搜索走过的路径称为引用链。当对象到GCRoots没有任何引用链时,即对象不可达时,对象即可回收。
GC Roots对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
即以下对象会成为GC Roots
• System class:被bootstrap或者system类加载器加载的类,比如rt.jar里的java.util.*;
• JNI local:native代码里的local变量,比如用户定义的JNI代码和JVM的内部代码;
• JNI global:native代码里的global变量;
• Thread block:当前活跃的线程block中引用的对象;
• Thread:已经启动并且没有stop的线程;
• busy monitor:被调用了wait()或者notify()或者被synchronized同步的对象,如果是synchronized方法,那么静态方法指的类,非静态方法指的是对象;
• java local:local变量,比如方法的入参和方法内创建的变量;
• native stack:native代码里的出入参数,比如file/net/IO方法以及反射的参数;
• finalizable:在一个队列里等待它的finalizer 运行的对象;
• unfinalized:一个有finalize方法的对象,还没有被finalize,同时也没有进入finalizer队列等待finalize;
• unreachable:不会被触碰到的对象,在MAT里被标记为root用来retain object,否则是不会在分析中出现的;
• java stack frame:java栈帧包含了本地变量,当dump被解析时且在preferences里设置过把栈帧当做对象,这时才会产生;
• unknown:位置的root类型。
下图表示可回收对象:

jvm8

阅读全文 »

一、自动内存管理:

JVM运行时数据区图

jvm1

1、程序计数器:
占用较小的空间,可以看作当前线程所执行字节码的行号指示器。分支,循环,跳转和异常处理都通过程序计数器来完成。
线程切换时恢复到字节码执行位置也依赖程序计数器,每个线程都有自己的程序计数器。

2、Java虚拟机栈
Java虚拟机栈是线程私有的,描述的是Java方法执行的内存模型。每个方法在执行时都会创建栈帧,用于存储操作数栈,局部变量表,动态链接和方法出口等信息;每个方法的从调用到执行完成,对应着一个栈帧的入栈到出栈的过程。
局部变量表存储基本的数据类型和引用类型,一般在编译时确认大小,在方法执行中不会改变局部变量表的大小;

阅读全文 »