目录
- 并行(Parallel): 多个Cpu(核)同时刻分别执行各自的一个进程(线程),互相不抢占CPU资源。
-
并发(Concurrent):单个Cpu(核)按时间片调度执行多个进程(线程)。
围绕并发访问共享变量有三种特性: 原子性、可见性、有序性
。
在并发包 java.util.concurrent
内各组件的实现上总结来说用到了以下两种方式:
- Unsafe包提供支持Volatile效果的读写 及 CAS 操作方法。 通常是:一个直到执行方法unsafe.compareAndSwapXXX 成功时才break的循环(while)结构体。eg. #getAndAddXXX 方法
- volatile 关键字修饰成员变量 , 或 每次都通过Unsafe#getXXXVolatile方法拿到最新主存的值。
LockSupport
并发包里底层实现很多都使用 LockSupport来控制线程的挂起和再次执行。unpark函数可以先于park调用,unpark提供可执行的许可(不可累加,一次性),park 时若存在许可则马上继续执行。
底层实现也依赖于sun.misc.Unsafe。 p.s: 反射Field#set的底层也用到了native Unsafe#putXXX 方法!
// 挂起线程
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
// 带挂起时长
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
// 唤醒
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
底层实现原理:
在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的; 维护了一个_counter的变量,简单说:当park时,这个变量被设置为0;当unpark时,这个变量被设置为1。
扩展:
1、一定是:在执行到某些(不限于 Object#wait、Thread#join、Thread#sleep 等)阻塞方法时,调用该线程的Interrupt()方法(thread1#interrupt();),阻塞状态下的线程将抛出一个InterruptedException中断异常被try-catch捕获处理,从而可以提早地终结被阻塞状态。 如果线程没有被阻塞,这时调用Interrupt()将不起作用,直到执行到阻塞方法才马上会抛出InterruptedException,捕获到InterruptedException后该线程中断标记就被清除。
2、Thread#join()的实现是 : 由子线程实例调用,循环判定它自己线程状态是活跃中,则wait(0)。
volidate
在多线程并发访问同一个共享缓存变量时,该关键字保证了:
- 可见性 (线程本地缓存的更新能写入到主存,同时也能被其他线程感知并失效它们本地缓存)
- 有序性 (禁止指令重排序, 遵守happens-before规则)。
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个全内存屏障(不同的CPU架构上内存屏障的实现不一样),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
CAS
sun.misc.Unsafe 提供了能够直接操作内存地址的native接口, 用来支持原子性。
通过自旋循环调用CAS, 汇编指令是:
- CPU多核:
lock cmpxchg
指令。 单个cmpxchg
指令并不是保证原子性, 是lock
进行加锁保证 ,优先 “锁缓存行”,最次才是“锁总线”。 - CPU单核: 使用
cmpxchg
指令。 因为是单核,所以没有并发问题。
- Unsafe#objectFieldOffset("类名".class.getDeclaredField("成员变量名") ) : 获得成员变量相对于类在内存地址上的 fieldOffset 。
- unsafe#arrayBaseOffset("数组类型".class) : 用来确定每个数组内元素的内存偏移量。
- unsafe#putXXXVolatile、unsafe#getXXXVolatile (XXX表示操作变量的类型) : 在获取、更新变量数据值时,像volidate关键字一样支持可见性、有序性。
- unsafe#compareAndSwapXXX (XXX表示操作变量的类型): CAS 更新某个实例对象 某个成员变量偏移量 的数值。
示例代码AtomicInteger的实现如下:
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // "value" 相对于本类实例的内存偏移量
static {
try {
// unsafe来获取valueOffset
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value; // volatile 修饰 保证 可见性和有序性
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// -- unsafe --
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// while CAS 判定更新成功后, volatile方式获取最新的数据值
v = this.getIntVolatile(o, offset);
} while(!this.compareAndSwapInt(o, offset, v, v + delta));
return v;
}
ConcurrentHashMap的实现里有数组偏移量的定义方式:
// ConcurrentHashMap
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
// Unsafe mechanics
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
// map里<key,vlaue>封装成的Node[]数组相较于整个类实例对象的内存offset
ABASE = U.arrayBaseOffset(ak);
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// ((long)index << ASHIFT) + ABASE Node[]数组各元素相较于整个类实例对象的内存offset
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}