java自旋锁的实现及其分析

2023-11-13

自旋锁是指一个线程尝试获取某个锁时,如果该锁已经被其他线程占用了,就一直循环检测锁释放被释放,而不是像互斥锁一样让线程进入挂起或者睡眠状态。

自旋锁的的缺点就是会一直死循环一直到获取锁为止,这样会一直消耗cpu内存,但是与互斥锁把线程阻塞,然后再次被唤醒相比在性能方面还是有优势的,因为频繁的从用户态切到内核态,需要消耗系统资源,性能也更惨,但是目前的jvm对synchronized实现做了修改采用自旋的到一定次数或者时间就进入阻塞状态,结合了自旋和阻塞,使得目前的synchronized性能大大提高了。

非公平的重入自旋锁的实现过程:

  • 加锁:判断cas中线程是否是当前线程,如果是则计数器+1,如果不是则死循环一直到修改成功(即获取锁成功),才结束循环

  • 解锁:判断当前获取到锁的线程是否是cas中的线程,如果是则看计数器重入了多少次,如果重入了直接计数器-1,否则直接通过cas修改释放锁。

  • /**
     * @Author:苏牧夕
     * @Date:2020/3/19 23:46
     * @Version 1.0
     */
    public class AtoLock {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger();
            System.out.println(atomicInteger.get());
            ThreadPoolExecutor poolExecutor = ThreadPool.InstancePool.poolExecutor;
            SpilLock spilLock = new SpilLock();
    //        FairSpilLock spilLock = new FairSpilLock();
            for (int i = 0; i < 5; i++) {
                int x = i;
               poolExecutor.execute(
                        () -> {
                            spilLock.lock();
                            spilLock.lock();
                            try {
                                System.out.println(Thread.currentThread().getName() + x + ",各种骚操作");
                                TimeUnit.SECONDS.sleep(3);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            spilLock.unlock();
                            spilLock.unlock();
                        }
                );
            }
    //        System.out.println("操作了几次:" + atomicInteger.get());
            poolExecutor.shutdown();
        }
        static class SpilLock implements Lock {
            private static AtomicReference<Thread> cas = new AtomicReference<>();
            private static AtomicInteger count = new AtomicInteger(0);
    
            @Override
            public void lock() {
                Thread thread = Thread.currentThread();
                if (thread==cas.get()){
                    count.incrementAndGet();
                    System.out.println(Thread.currentThread().getName()+"上锁成功");
                    return;
                }
                while (!cas.compareAndSet(null, thread)) {
                }
                System.out.println(Thread.currentThread().getName()+"上锁成功");
    
            }
    
            @Override
            public void lockInterruptibly() throws InterruptedException {
    
            }
    
            @Override
            public boolean tryLock() {
                return false;
            }
    
            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                Thread thread = Thread.currentThread();
                Long timeout= unit.toNanos(time);
                if (cas.get()==thread){
                    count.incrementAndGet();
                    return true;
                }
                Long start = System.nanoTime();
                while (!cas.compareAndSet(null,thread)){
                    if (thread.isInterrupted()){
                        thread.interrupt();
                        throw  new InterruptedException();
                    }
                    long tt=System.nanoTime()-start;
                    if (tt>=timeout){
                        return false;
                    }
                }
                return true;
            }
    
            public  void unlock() {
                Thread thread = Thread.currentThread();
                if (thread==cas.get()){
                    if (count.get()>0){
                System.out.println(Thread.currentThread().getName()+"解锁成功");
                        count.decrementAndGet();
                    }else{
                        if (cas.compareAndSet(thread, null)) {
                System.out.println(Thread.currentThread().getName()+"解锁成功");
                        }
                    }
                }
            }
    
            @Override
            public Condition newCondition() {
                return null;
            }
        }
    }
    
    
  • 公平自旋锁

    逻辑:

    • 加锁:一个标记记录当前获得锁的编号currNo,然后给每条线程匹配一个编号,如果当前锁编号和当前线程的编号匹配则解锁自旋
    • 解锁:通过cas修改 锁的编号currNo,改为当前获得锁的线程的编号+1
    
    static class FairSpilLock implements Lock{
       AtomicInteger currNo = new AtomicInteger(0);
       AtomicInteger threadNo = new AtomicInteger(0);
       ThreadLocal<Integer> local = new ThreadLocal<>();
       AtomicInteger count = new AtomicInteger(0);
           @Override
           public void lock() {
           //生成一个线程编号
               int thNo = threadNo.getAndIncrement();
               //把编号和线程联系起来
               local.set(thNo);
               //自旋当前锁的编号curr释放和当前线程编号一样,如果一样则结束自旋
               while (thNo!=currNo.get()){
                   Thread.yield();//如果不是则当前线程礼让,可以不加,加了减少循环次数
               }
               System.out.println(Thread.currentThread().getName()+"加锁成功");
    
           }
    
           @Override
           public void lockInterruptibly() throws InterruptedException {
    
           }
    
           @Override
           public boolean tryLock() {
               return false;
           }
    
           @Override
           public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
               return false;
           }
    
           @Override
           public void unlock() {
           //获取当前线程的编号
               Integer thNo = local.get();
               //cas修改当前锁编号
               if (currNo.compareAndSet(thNo,thNo+1)){
                   System.out.println(Thread.currentThread().getName()+"解锁成功!");
               }
    
           }
    
           @Override
           public Condition newCondition() {
               return null;
           }
       }
    

    优点:实现了公平性,使得每条线程都有机会执行

    缺点:每条线/进程都占用处理器在读写标记变量,导致系统性能降低

  • CLH公平自旋锁

    来解决频繁读写标记,就出现了CLH自旋锁算法

    首先简单说一下ThreadLocal这个类的作用,该类负责提供线程都有的本地变量(局部变量),在该类存储的变量,在整个线程存活的过程中可以使用,但是其他线程是无法取的其他线程的局部变量的,类似于一每条线程都有一个私有的Map存储参数,通常使用该类来存储线程的上下文或者id,又因为变量都是线程内部的所以不存在并发问题,所以是线程安全的。

    CLH算法则是利用了ThreadLocal来存储每条线程的节点和它前一条线程的节点作为前继节点,每条线程只自旋判断前继节点的状态,如果前继节点释放锁状态改变,则本线程获取到锁,整个过程在模拟队列,但是该队列是非真实存在的,只是逻辑上是队列,也正是因为通过模拟队列来每条线程有序执行,从而达到公平。

    那么多个线程直接是如何链接起来的呢,首先每条线程都有一个节点用于保存自身的状态是否加锁或者解锁状态,并且存储在ThreahLocal,同时也存储来前一条线程的节点作为,该线程前继节点,那么每条线程是如何获取前一个线程的节点作为前继节点的呢,这里我们需要使用原子引用类来存储尾节点,每条线程创建的时候通过cas操作,把自身作为尾节点,把前一个尾节点作为其前继节点,也就是做,每次有线程申请锁就获取保存了前一条线程节点的尾节点作为其前继节点,这样就把多条线程链接起来,而且还是有序的。

    此外,ThreadLocal容易出现内存泄漏,为什么会出现内存泄漏呢?因为该类内部使用Map存储,而且key继承软引用类,然后value映射起来的,只有内存不足,gc进行垃圾回收就会把软引用回收,也就是key回收了,但是value还在,而且是无法获取的,因为key已经被回收了。

    CLH锁逻辑上是链表形式,所以同样具有很好的扩展性,性能也比较高。

    public class AtoLock {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger();
            System.out.println(atomicInteger.get());
            ThreadPoolExecutor poolExecutor = ThreadPool.InstancePool.poolExecutor;
    //        SpilLock spilLock = new SpilLock();
    //        FairSpilLock spilLock = new FairSpilLock();
            CLHSpilLock spilLock = new CLHSpilLock();
            for (int i = 0; i < 5; i++) {
                int x = i;
               poolExecutor.execute(
                        () -> {
                            spilLock.lock();
    //                        spilLock.lock();
                            try {
                                System.out.println(Thread.currentThread().getName() + x + ",各种骚操作");
                                TimeUnit.SECONDS.sleep(3);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    //                        spilLock.unlock();
                            spilLock.unlock();
                        }
                );
            }
    //        System.out.println("操作了几次:" + atomicInteger.get());
            poolExecutor.shutdown();
        }
        static class CLHSpilLock implements Lock{
            CLHSpilLock() {
                tail = new AtomicReference<Node>(new Node());
                //init方法有延迟加载的作用
                prev = new ThreadLocal<Node>(){
                    @Override
                    protected Node initialValue() {
                        return null;
                    }
                };
                currNode = new ThreadLocal<Node>(){
                    @Override
                    protected Node initialValue() {
                        return new Node();
                    }
                };
            }
    
            static class Node{
                //用于判断前继节点释放释放锁。
                private volatile boolean locked;
            }
    
            //
           private final ThreadLocal<Node>prev;
            //线程的本地变量(局部变量),存储自身节点的状态
           private final ThreadLocal<Node>currNode;
           //存储尾节点(也是前一条线程的节点,后一条线程通过这个tail获取前一条线程变量的内存地址并且设置尾前继节点)
           private final  AtomicReference<Node> tail ;
    
    
            @Override
            public void lock() {
                //ThreahLocal存储的本地变量(局部变量),只有线程自己才可以访问到,其他的线程获取不到
                //相当于是线程的私有内存
                //获取当前线程的节点标记。
              final   Node node = currNode.get();
              //设置为当等锁的自旋状态
                node.locked=true;
                //通过cas为节点设置为当前变量,并且获取之前的尾节点作为前继节点
                //这一步是线程之间执行顺序连接起来,preNode是前一条线程的节点,
                Node predNode = tail.getAndSet(node);
                prev.set(predNode);
                //注意CLH,通过把前继节点和自身的节点存存储在ThreadLocal中,
                // 每条线程只根据自己的前继节点释放锁来判断自己释放获取到锁。
                //自旋当前线程节点的前继节点释放已经释放锁,如果释放锁则停止自旋。
                while (predNode.locked){
                }
                System.out.println(Thread.currentThread().getName()+"加锁成功");
            }
    
            @Override
            public void lockInterruptibly() throws InterruptedException {
    
            }
    
            @Override
            public boolean tryLock() {
                return false;
            }
    
            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                return false;
            }
    
            @Override
            public void unlock() {
                //获取线程的私有节点,然后修改状态为false表示释放锁,因为locked使用了volavte修饰,
                //当前节点的状态是下一条线程自旋判断前继节点的状态,因此修改为false时它里面可以发现状态改变,
                // volatilt保证变量在线程之间的可进行
               final Node node = currNode.get();
                node.locked=false;
                //然后把当前节点从本地变量(局部变量)(线程本地内存,反正就是只有自己可以看到的变量)中移除防止内存溢出
                currNode.remove();
                System.out.println(Thread.currentThread().getName()+"释放锁成功");
            }
    
    
            @Override
            public Condition newCondition() {
                return null;
            }
        }
      }
    

    总结:CLH是逻辑上的队列(链表),并不是真正的队列,而且自旋是自旋前继节点,根据前继节点来判断是否获得锁。

  • MCS公平自旋锁

    MCS算法和CLH算法思路非常相似,但是而且最大的区别是,MCS自旋的是自身节点,而不是前继节点,同时MCS是真正的链表,

    算法思想:每条线程都有一个自身的节点Node(Node存储着线程当前锁的状态和后继节点),存储在线程的局部变量ThreadLocal中,通过一个原子类存储上一次最新申请获取锁的线程的节点称为尾节点,并且后续线程通过cas,获取上一次最后的节点,并且把当前线程节点设置为尾节点,然后把上一次尾节点的后继节点设置为当前节点,然后当前节点进行自旋,那么自旋是如何解锁的呢,这通过解锁过程中,把当前线程节点的后继节点的状态修改为获得锁状态(每次修改后继节点的状态和链表保证了线程执行的有序性)。

    /**
     * @Author:苏牧夕
     * @Date:2020/3/19 23:46
     * @Version 1.0
     */
    public class AtoLock {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger();
            System.out.println(atomicInteger.get());
            ThreadPoolExecutor poolExecutor = ThreadPool.InstancePool.poolExecutor;
    //        SpilLock spilLock = new SpilLock();
    //        FairSpilLock spilLock = new FairSpilLock();
    //        CLHSpilLock spilLock = new CLHSpilLock();
            MCSSpilLock spilLock = new MCSSpilLock();
            for (int i = 0; i < 5; i++) {
                int x = i;
                poolExecutor.execute(
                        () -> {
                            spilLock.lock();
    //                        spilLock.lock();
                            try {
                                System.out.println(Thread.currentThread().getName() + x + ",各种骚操作");
                                TimeUnit.SECONDS.sleep(3);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    //                        spilLock.unlock();
                            spilLock.unlock();
                        }
                );
            }
    //        System.out.println("操作了几次:" + atomicInteger.get());
            poolExecutor.shutdown();
        }
    
        static class MCSSpilLock implements Lock {
            private final ThreadLocal<Node> curnode;
            private AtomicReference<Node> tail;
            public MCSSpilLock() {
                //第一条线程直接初始化当前节点和尾节点
                this.curnode = new ThreadLocal<Node>() {
                    @Override
                    protected Node initialValue() {
                        return new Node();
                    }
                };
                tail = new AtomicReference<Node>();
            }
    
            @Override
            public void lock() {
                //获取当前线程局部变量是否已经存在node节点
                Node currNode = curnode.get();
                //线程的节点为null,则创建一个节点
                if (currNode == null) {
                    currNode = new Node();
                }
                //然后通过尾插法,先把尾节点取处理,并把当前节点设为尾节点。
                Node predNode = tail.getAndSet(currNode);
                if (predNode != null) {
                    //如果不是第一个节点则设置为true,处于等待锁状态
                    currNode.locked = true;
                    //把前一条线程的节点后继节点设置尾当前节点,保持公平
                    predNode.next = currNode;
                }
                //轮询当前节点
                while (currNode.locked) {}
                System.out.println(Thread.currentThread().getName()+"上锁成功");
            }
    
            @Override
            public void lockInterruptibly() throws InterruptedException {
    
            }
    
            @Override
            public boolean tryLock() {
                return false;
            }
    
            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                return false;
            }
    
            @Override
            public void unlock() {
                //获取当前线程的节点
                Node node = curnode.get();
                //如果是最后一个节点则把tail也进行回收。
                if (node.next == null) {
                    tail.compareAndSet(node, null);
                }
                //如果有后继线程,则修改状态为获取锁状态。并且把当前节点的引用设置为null
                if (node.next != null) {
                    node.next.locked = false;
                    node.next = null;
                    //回收当前线程的节点。防止内存泄漏
                    curnode.remove();
                }
                System.out.println(Thread.currentThread().getName()+"解锁成功");
            }
    
            @Override
            public Condition newCondition() {
                return null;
            }
    
            static class Node {
                //false表示获得锁。
                private volatile boolean locked = false;
                private Node next;
            }
        }
    
    

    参考CLH和MCS

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java自旋锁的实现及其分析 的相关文章

  • 为 JSP 创建注销链接?

    当用户登录我的应用程序时 他提交一个要通过 Servlet 处理的表单 servlet 为用户创建一个会话 我如何创建一个链接以便用户可以注销 我似乎无法直接链接到 Servlet 如何删除会话并链接回主页 HttpSession sess
  • SharePoint 2010 Web 服务上的 Java JBoss 401 错误

    我的代码在 Eclipse IDE 中测试时运行成功 我正在使用生成的 Copy wsdl 通过 Web 服务连接到 MS SharePoint 2010 当我在 JBoss 服务器上部署代码 运行 Adob e LifeCycle 时 我
  • Android 服务 START_STICKY START_NOT_STICKY

    我需要让我的服务始终在后台运行 并使用 startService 函数启动我的服务 无论应用程序的状态如何 我都不想重新启动服务 这是我的观察 START STICKY gt 如果应用程序启动 则服务正在重新启动 当应用程序关闭时 服务也会
  • 从 Windows Batch (cmd.exe) 中的文件读取环境变量

    我正在尝试从批处理文件中读取变量 以便稍后在批处理脚本 Java 启动器 中使用 理想情况下 我希望所有平台 Unix Windows 上的设置文件都具有相同的格式 并且也是有效的 Java 属性文件 也就是说 它应该看起来像这样 sett
  • java中的散列是如何工作的?

    我正在尝试弄清楚java中的哈希值 例如 如果我想在哈希图中存储一些数据 它是否会有某种带有哈希值的底层哈希表 或者 如果有人能够对哈希的工作原理给出一个很好且简单的解释 我将非常感激 HashMap 基本上在内部实现为数组Entry 如果
  • 将 Swing 集成到简单的文本冒险游戏中

    我对 Java 中的一些中级概念相当陌生 最近 我制作了一款名为 DazzleQuest 的文本冒险游戏 它完全在开发者控制台 终端中运行 它涉及到我的朋友作为角色 所以我想向他们展示它 并通过将命令行的功能和控制台的输出转移到一个简单的
  • 当Java中set已经是原子的时候,为什么我们还需要compareAndSet呢?

    因为原子意味着线程安全 当 set 本身在java中是原子和线程安全的时候 我们什么时候使用compareAndSet 举例来说 我想以原子方式设置一个变量 以便每个其他线程都可以看到它 但我希望以线程安全的方式设置该变量 我可以简单地将其
  • 具有多个字符串的列表视图

    我正在尝试创建一个包含多个字符串的列表视图 现在我有一个可以实现的功能 while i lt 10 GETS DATA FROM WEBPAGE ETC a DATAFROMWEBPAGE1 b DATAFROMWEBPAGE2 c DAT
  • 如何在 WebSphere Liberty Batch 中配置事务超时?

    的作用是什么javax transaction global timeout 我是否需要实施检查点 超时 中的方法检查点算法 服务器配置级别有什么东西吗 它如何与应用程序级别的设置进行交互 2016年12月2日编辑 重新设计并解释了为应用程
  • 在字节数组上进行右位旋转/循环移位的最快方法是什么

    如果我有数组 01101111 11110000 00001111 111 240 15 移位 1 位的结果是 10110111 11111000 00000111 183 248 7 数组大小不固定 移位范围为 1 到 7 含 目前我有以
  • toArray 与预先确定大小的数组

    使用时ar toArray new String ar size 安卓工作室3 2 1警告预先确定大小的数组并建议空数组 有两种方式将集合转换为数组 使用 预先确定大小的数组 如 c toArray new String c size 或使
  • BODMAS系统的加法和减法

    我一直在构建一个简单的公式计算器 但一直被加法和减法困扰 正如您应该知道的 在计算方程时 您遵循优先级算术规则 即括号 顺序 幂函数 除法 乘法 加法和减法 问题是加法和减法具有相同的优先级 因此您可以从左到右阅读 到目前为止 这是我的代码
  • 错误:类 kotlin.reflect.jvm.internal.FunctionCaller$FieldSetter

    我已尝试一切方法来消除此错误 但它不断出现 Class kotlin reflect jvm internal FunctionCaller FieldSetter can not access a member of class com
  • 如何查找类路径中具有指定名称的所有资源?

    我想列出类路径中具有特定名称的所有文件 我预计会发生多次 因此Class getResource String 不管用 基本上 我必须识别类路径中任何位置具有特定名称 例如 xyz properties 的所有文件 然后累积读取其中的元数据
  • CXF 增加连接池大小而不更改 http.maxConnections

    最近我被要求将 CXF 配置为与我们旧的 XFire 服务相同的参数 这些参数之一是Keep Alive timeout 60 max 20 然而 我做了一些研究 看来 CXF 使用 JVMHttpURLConnection引擎盖下的对象
  • 在 x64 系统上使用 skype-java-api

    我正在使用 skype java api 在 Java 中使用 Skype 我需要的唯一功能是点击即可拨打电话号码 它在 Windows XP x86 上运行良好 但我刚刚在 Windows 7 x64 上测试它 但失败了 错误是 线程 T
  • 避免加密和编码的 URL 字符串中的换行符

    我正在尝试实现一个简单的字符串编码器来混淆 URL 字符串的某些部分 以防止它们被用户弄乱 我使用的代码几乎与示例中的相同JCA指南 http docs oracle com javase 6 docs technotes guides s
  • 不幸的是 Project_Name 已停止

    我有一个简单的应用程序 您可以在文本视图中输入文本并按提交 它会在另一个活动中显示文本 然而 当我按下提交时 给我消息 不幸的是 发送已停止 我查看了SO上的其他线程 但是不幸的是 myfirstproject 在 java 中停止工作错误
  • 如何在不下载子项的情况下从 Firebase 获取子项密钥?

    我有一个 Firebase 数据库 其中的节点 items 有很多子项 我想导入子项键的列表 由于每个子项都包含相当多我对此不感兴趣的数据 因此我想仅下载子项密钥 以最大程度地减少传输的数据量 为了便于说明 假设我有以下数据结构 然后我想获
  • 在android中测量不规则多边形的面积

    我正在开发一个应用程序 在其中我在地图上绘制多边形 并且我使用的地图不是谷歌 它的Mapsforge开源离线地图库 我可以通过将地理点转换为像素点来轻松在地图上绘制多边形 但在这里我想发现是不规则的多边形 为此我做了很多尝试 但它让我失败了

随机推荐

  • Python最强学习知识点:面相对象基础语法

    面相对象基础语法 目标 dir 内置函数 定义简单的类 只包含方法 方法中的 self 参数 初始化方法 内置方法和属性 01 dir 内置函数 知道 在 Python 中 对象几乎是无所不在的 我们之前学习的 变量 数据 函数 都是对象
  • 单周期RISC-V架构CPU的设计---设计篇

    目录 一 模块设计 1 pc reg v 1 1 功能说明 1 2 整体框图 1 3 接口列表 1 4 内部信号说明 1 5 关键电路 2 id v 2 1 功能说明 2 2 整体框图 2 3 接口列表 2 4 内部信号说明 2 5 关键电
  • Vim语法检查插件cppSyntaxCheck

    下载cppSyntaxCheck master https github com sjp 1024 Classroom notes blob master cppSyntaxCheck master zip 将解压文件中的cppSyntax
  • 40+野路子学习软件编程记录

    1 软件几乎零基础 机械专业 手机行业项目管理工作 2 疫情期间 闲来无事 突发兴趣 3 有点开窍入门 4 树莓派Linux学习入手 openwrt homeassiant airplay和媒体局域网服务等 5 学习Python读ds18b
  • postgresql导出表结构

    pg dump命令可以导出数据库中的表结构 s 选项用来只导出表结构 而不会导出表中的数据 t 选项用来指定要导出的数据库表 pg dump s t tlb exampledb gt tmp tlb exampledb是数据库 tlb是ex
  • MySQL对大小写敏感吗

    见字如面 见标题知内容 你有遇到过因为MYSQL对大小写敏感而被坑的体验吗 之前看过阿里巴巴Java开发手册 在MySql建表规约里有看到 强制 表名 字段名必须使用小写字母或数字 禁止出现数字开头 禁止两个下划线中间只 出现数字 数据库字
  • Python代码:根据txt文件批量提取图片

    个人微信公众号 AI研习图书馆 欢迎关注 深度学习知识及资源分享 学习交流 共同进步 1 介绍 Python代码 根据txt文件批量提取图片并保存至另一文件夹 用于深度学习 图片数据预处理 2 Python代码 实现方案一 import s
  • vue点击按钮跳转新页面

    const str location href split 0 window open str router的name
  • java自动登录 selenium 自动登录并获取cookie

    选择操作网页 我用的edge 谷歌我的版本太高没有对应的驱动 下载Edge的驱动程序 直接解压就好里面只有一个 exe文件 https developer microsoft com en us microsoft edge tools w
  • bluez调试笔记

    蓝牙系列 bluez调试笔记 weixin 41069709的博客 CSDN博客 bluezbluez移植https blog csdn net weixin 41069709 article details 125168114 spm 1
  • 指针与const限定符

    const限定符和指针结合起来常见的情况有以下几种 const int a int const a 这两种写法是一样的 a是一个指向const int的指针 a所指向的内存单元不可改写 所以 a 是不允许的 但a可以改写 所以a 是允许的
  • 低电压电池充不进电问题分析

    作者 AirCity 2020 3 1 Aircity007 sina com 本文所有权归作者Aircity所有 1 问题现象 某MSM8998手机项目 老化测试一端时间后 有很小比例的机器关机黑屏 充电1h没有反应 测量电池电压 2 6
  • Java实验二 货物进销管理系统【简单易懂】

    写在前面 这个题目主要通过仔细阅读题目的需求 再通过对文件流以及Vector ArrayList等容器的控制来完成整个代码体系的构建 因为当时正在学习 对代码的构建逻辑不够规范 思想不够到位的地方也难以避免 在这里抱歉抱歉 不过这一版的构建
  • 在K2P路由器,非官方openwrt固件,安装软件遇到的坑!

    手上有一台斐讯K2P A2版本的路由器 一直空闲在宿舍 最近发现这个路由器被破解了 可以刷上不死breed 还有很多大神做的固件 因此我想用它刷上openwrt系统后 安装某软件 从而直接在路由器上完成学校的宽带验证登陆 接下来我就介绍我在
  • 04 Cesium—Cesium ion介绍

    文章中所有操作均是在 Cesium 1 91 版本下进行的 其它版本差异请自行适配 Cesium ion Cesium ion 是一个提供瓦片图和3D地理空间数据的平台 Cesium ion 支持把数据添加到用户自己的 CesiumJS 应
  • 8421BCD码 5421BCD码 余三码 格雷码 余三循环码之间的关系,转换以及简易方法

    8421BCD码 5421BCD码 余三码 格雷码 余三循环码之间的关系 转换以及简易方法 1 有权码和无权码的包括 2 各种码值的介绍 8421码的简介 8421码又称为BCD码 是十进代码中最常用的一种 在这种编码方式中 每一位二值代码
  • java设计模式之单例模式

    目录 一 单例模式 二 饿汉模式和懒汉模式 1 饿汉模式 线程安全 2 懒汉模式
  • KVM无法进入virt-manager,提示Unable to init server: Could not connect: Connection refused

    1 KVM virt manager不能以root用户进入 需切换成普通用户或者sudo用户 2 需要配置ssh 密钥 3 需要安装Xming或者Xmanager等KVM可用等图形界面软件 无法连接kvm 设置用户到组 一定要当前用户不要r
  • 应用安全系列之九:HTTP参数污染

    本系列文章主旨在于介绍一些漏洞类型产生的基本原理 探索最基础的解决问题的措施 不排除有些语言或者系统提供的安全的API可以更好地更直接地解决问题 也不排除可以严格地输入验证来解决 URL参数注入 也称为HPP HTTP Parameter
  • java自旋锁的实现及其分析

    自旋锁是指一个线程尝试获取某个锁时 如果该锁已经被其他线程占用了 就一直循环检测锁释放被释放 而不是像互斥锁一样让线程进入挂起或者睡眠状态 自旋锁的的缺点就是会一直死循环一直到获取锁为止 这样会一直消耗cpu内存 但是与互斥锁把线程阻塞 然