import com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* 自旋锁。
* <LI>以原子性的设置当前线程作为临界条件,控制多个线程的访问,达到每次只有一个线程获取临界对象,其他线程等待</LI>
* <LI>无法获取锁时,不停的循环直到设置成功</LI>
* Created by 张三丰 on 2017-07-15.
*/
public class SpinLock {
/**
* 原子性的操作
*/
private AtomicReference<Thread> lockProvider = new AtomicReference<>();
/**
* 设置当前线程,占用lockProvider,设置成功表示获取锁
*/
public void lock() {
Thread thread = Thread.currentThread();
//进入自旋的临界条件:lockProvider已经被其他线程设置过
while (!lockProvider.compareAndSet(null, thread)) {
//不停自旋,直到compareAndSet成功
//当前线程一直在占用cpu,线程状态未改变
}
}
/**
* 不再占用lockProvider
*/
public void unlock() {
Thread thread = Thread.currentThread();
//持有锁的线程调用才能成功
boolean compareAndSet = lockProvider.compareAndSet(thread, null);
}
}
class SpinTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
SpinLock lock = new SpinLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
}
}
/**
* 解决SpinLock的公平性问题。TicketLock按照FIFO顺序处理线程
*/
class TicketLock {
//服务的号码,从0开始递增
private AtomicInteger serveNumber = new AtomicInteger();
//号牌号码,从0开始,每个线程递增获取
private AtomicInteger ticketNumber = new AtomicInteger();
//以上2个数字一致,则表示正在服务某个号牌持有者的线程
//每个线程获取ticket之后保存在这里
private static final ThreadLocal<Integer> ticketHolder = new ThreadLocal<>();
/**
* 根据号码自旋直到服务自己
*/
public void lock() {
int ticket = ticketNumber.getAndIncrement();
ticketHolder.set(ticket);
//进入自旋的临界条件:当前线程拿到的号码与服务号码不匹配
while (serveNumber.get() != ticket) {
//自旋
}
}
/**
* 把服务号码递增,解除下一个自旋
*/
public void unlock() {
if (serveNumber.get() == ticketHolder.get()) {
//正在服务的线程的调用才有效
//服务号码递增
serveNumber.incrementAndGet();
}
}
}
class TicketTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
TicketLock lock = new TicketLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了SpinLock的公平性问题,但是有属性被所有线程访问,对改属性的更改需要频繁刷新JMM中公共内存
}
}
/**
* 解决TicketLock的serveNumber属性被所有线程访问的问题.
* <LI>通过链表的形式保证顺序(公平性),同时每个节点只关注前一节点的isLocked属性</LI>
* <LI>临界条件:节点属性isLocked</LI>
*/
class CLHLock {
static class Node {
/**
* 节点的锁定状态
*/
volatile boolean isLocked = true;
Node next;
}
private volatile Node tailNode;
private static final ThreadLocal<Node> NODE_HOLDER = new ThreadLocal<>();
private static final AtomicReferenceFieldUpdater<CLHLock, Node> FIELD_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tailNode");
/**
* 建立当前节点与前一个节点的关联,并监听前一个节点的锁定状态
*/
public void lock() {
Node node = new Node();
NODE_HOLDER.set(node);
Node preNode = FIELD_UPDATER.getAndSet(this, node);
if (preNode != null) {
//临界条件:前一个节点被锁定
while (preNode.isLocked) {
//自旋
}
}
}
/**
* 把当前节点的锁定状态置为false,解除自旋的监听当前节点锁定状态的线程
*/
public void unlock() {
Node node = NODE_HOLDER.get();
node.isLocked = false;
}
}
class CLHTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
CLHLock lock = new CLHLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了Ticket锁的公共属性的问题,但是访问的是上一个线程持有的节点的属性,在NUMA架构下,可能造成频繁访问远程内存问题
}
}
/**
* 同自旋自己节点的属性,解决CLH锁自旋pre节点,在NUMA架构下内存读取比较慢的问题
*/
class MCSLock {
static class MCSNode {
//是否被锁定,默认是true
//volatile
boolean isLocked = true;
MCSNode next;
}
private static final ThreadLocal<MCSNode> NODE_HOLDER = new ThreadLocal<>();
volatile MCSNode tail;
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "tail");
/**
* 自旋当前节点等待锁
*/
public void lock() {
//创建当前节点
MCSNode node = new MCSNode();
NODE_HOLDER.set(node);
//获取之前的节点,即尾节点
MCSNode preTail = UPDATER.getAndSet(this, node);//第1步 设置当前节点并且获取之前的节点,当前节点变为尾节点
if (preTail != null) { //说明之前已经有节点,线程已经被锁定了,那么就自旋等待
preTail.next = node;//第2步 关联节点
while (node.isLocked) {//第3步 自旋等待
//自旋
}
}
}
/**
* 解除next节点的自旋锁<br>
* lock在等待当前节点的状态,unlock需要检查next节点是否存在,如果存在置为false,以结束其他线程的自旋
*/
public void unlock() {
MCSNode node = NODE_HOLDER.get();
//如果当前节点是尾节点,置为null。(当前线程已经结束,后面不能挂下一个节点了(挂了节点后无法取消其自旋))
if (!UPDATER.compareAndSet(this, node, null)) {
while (node.next != null) {//使用while循环,是因为lock里面第1步执行成功,可能第2步还没执行完成
node.next.isLocked = false;
node.next = null;// for GC
}
} else {
if (node.next != null) {
node.next.isLocked = false;
node.next = null;// for GC
}
}
}
}
class MCSTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
MCSLock lock = new MCSLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了CLH锁可能频繁访问远程内存的问题
}
}
附:涉及到的处理器架构知识
import com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* 自旋锁。
* <LI>以原子性的设置当前线程作为临界条件,控制多个线程的访问,达到每次只有一个线程获取临界对象,其他线程等待</LI>
* <LI>无法获取锁时,不停的循环直到设置成功</LI>
* Created by 张三丰 on 2017-07-15.
*/
public class SpinLock {
/**
* 原子性的操作
*/
private AtomicReference<Thread> lockProvider = new AtomicReference<>();
/**
* 设置当前线程,占用lockProvider,设置成功表示获取锁
*/
public void lock() {
Thread thread = Thread.currentThread();
//进入自旋的临界条件:lockProvider已经被其他线程设置过
while (!lockProvider.compareAndSet(null, thread)) {
//不停自旋,直到compareAndSet成功
//当前线程一直在占用cpu,线程状态未改变
}
}
/**
* 不再占用lockProvider
*/
public void unlock() {
Thread thread = Thread.currentThread();
//持有锁的线程调用才能成功
boolean compareAndSet = lockProvider.compareAndSet(thread, null);
}
}
class SpinTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
SpinLock lock = new SpinLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
}
}
/**
* 解决SpinLock的公平性问题。TicketLock按照FIFO顺序处理线程
*/
class TicketLock {
//服务的号码,从0开始递增
private AtomicInteger serveNumber = new AtomicInteger();
//号牌号码,从0开始,每个线程递增获取
private AtomicInteger ticketNumber = new AtomicInteger();
//以上2个数字一致,则表示正在服务某个号牌持有者的线程
//每个线程获取ticket之后保存在这里
private static final ThreadLocal<Integer> ticketHolder = new ThreadLocal<>();
/**
* 根据号码自旋直到服务自己
*/
public void lock() {
int ticket = ticketNumber.getAndIncrement();
ticketHolder.set(ticket);
//进入自旋的临界条件:当前线程拿到的号码与服务号码不匹配
while (serveNumber.get() != ticket) {
//自旋
}
}
/**
* 把服务号码递增,解除下一个自旋
*/
public void unlock() {
if (serveNumber.get() == ticketHolder.get()) {
//正在服务的线程的调用才有效
//服务号码递增
serveNumber.incrementAndGet();
}
}
}
class TicketTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
TicketLock lock = new TicketLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了SpinLock的公平性问题,但是有属性被所有线程访问,对改属性的更改需要频繁刷新JMM中公共内存
}
}
/**
* 解决TicketLock的serveNumber属性被所有线程访问的问题.
* <LI>通过链表的形式保证顺序(公平性),同时每个节点只关注前一节点的isLocked属性</LI>
* <LI>临界条件:节点属性isLocked</LI>
*/
class CLHLock {
static class Node {
/**
* 节点的锁定状态
*/
volatile boolean isLocked = true;
Node next;
}
private volatile Node tailNode;
private static final ThreadLocal<Node> NODE_HOLDER = new ThreadLocal<>();
private static final AtomicReferenceFieldUpdater<CLHLock, Node> FIELD_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tailNode");
/**
* 建立当前节点与前一个节点的关联,并监听前一个节点的锁定状态
*/
public void lock() {
Node node = new Node();
NODE_HOLDER.set(node);
Node preNode = FIELD_UPDATER.getAndSet(this, node);
if (preNode != null) {
//临界条件:前一个节点被锁定
while (preNode.isLocked) {
//自旋
}
}
}
/**
* 把当前节点的锁定状态置为false,解除自旋的监听当前节点锁定状态的线程
*/
public void unlock() {
Node node = NODE_HOLDER.get();
node.isLocked = false;
}
}
class CLHTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
CLHLock lock = new CLHLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了Ticket锁的公共属性的问题,但是访问的是上一个线程持有的节点的属性,在NUMA架构下,可能造成频繁访问远程内存问题
}
}
/**
* 同自旋自己节点的属性,解决CLH锁自旋pre节点,在NUMA架构下内存读取比较慢的问题
*/
class MCSLock {
static class MCSNode {
//是否被锁定,默认是true
//volatile
boolean isLocked = true;
MCSNode next;
}
private static final ThreadLocal<MCSNode> NODE_HOLDER = new ThreadLocal<>();
volatile MCSNode tail;
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "tail");
/**
* 自旋当前节点等待锁
*/
public void lock() {
//创建当前节点
MCSNode node = new MCSNode();
NODE_HOLDER.set(node);
//获取之前的节点,即尾节点
MCSNode preTail = UPDATER.getAndSet(this, node);//第1步 设置当前节点并且获取之前的节点,当前节点变为尾节点
if (preTail != null) { //说明之前已经有节点,线程已经被锁定了,那么就自旋等待
preTail.next = node;//第2步 关联节点
while (node.isLocked) {//第3步 自旋等待
//自旋
}
}
}
/**
* 解除next节点的自旋锁<br>
* lock在等待当前节点的状态,unlock需要检查next节点是否存在,如果存在置为false,以结束其他线程的自旋
*/
public void unlock() {
MCSNode node = NODE_HOLDER.get();
//如果当前节点是尾节点,置为null。(当前线程已经结束,后面不能挂下一个节点了(挂了节点后无法取消其自旋))
if (!UPDATER.compareAndSet(this, node, null)) {
while (node.next != null) {//使用while循环,是因为lock里面第1步执行成功,可能第2步还没执行完成
node.next.isLocked = false;
node.next = null;// for GC
}
} else {
if (node.next != null) {
node.next.isLocked = false;
node.next = null;// for GC
}
}
}
}
class MCSTester {
public static void main(String[] args) throws InterruptedException {
//测试锁,循环1000次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 1000
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(10);
MCSLock lock = new MCSLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock();
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
lock.unlock();
});
}
// executorService.awaitTermination(5, TimeUnit.SECONDS);
// //所有线程执行完毕后,查看最终结果
// System.out.println("===========" + sharedValue.get(0));
executorService.shutdown();
//优缺点:
//线程状态不改变,没有线程上下文的切换,响应速度快
//一直占用CPU,所以自旋线程比较多时(竞争比较激烈),性能下降明显
//适合竞争不太激烈,持有锁的时间很短的场景
//解决了CLH锁可能频繁访问远程内存的问题
}
}
1 SMP(Symmetric Multi-Processor)
对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。
SMP能够保证内存一致性,但这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,
可能会导致CPU资源的浪费。常用的PC机就属于这种。
2 NUMA(Non-Uniform Memory Access)
非一致存储访问,将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,
访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问的由来。NUMA较好地解决SMP的扩展问题,
当CPU数量增加时,因为访问远地内存的延时远远超过本地内存,系统性能无法线性增加