显式锁之自旋锁

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数量增加时,因为访问远地内存的延时远远超过本地内存,系统性能无法线性增加


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值