热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用Java锁定自由堆栈

使用Java锁定自由堆栈原文:https://www.gee

使用 Java 锁定自由堆栈

原文:https://www.geeksforgeeks.org/lock-free-stack-using-java/

在多线程环境中,无锁算法提供了一种方式,线程可以访问共享资源,而没有锁的复杂性,也不会永远阻塞线程。这些算法成为程序员的选择,因为它们提供了更高的吞吐量并防止了死锁。

这主要是因为设计基于锁的算法,对于并发性带来了自己的挑战。编写高效的锁和同步以减少线程争用的复杂性不是每个人都喜欢的。此外,即使在编写了复杂的代码之后,在生产环境中也会出现很多难以发现的错误,其中涉及多个线程,这变得更加难以解决。

从这个角度来看,我们将讨论如何将无锁算法应用于 Java 中广泛使用的数据结构之一,称为 堆栈 。众所周知,堆栈用于许多实际应用中,如文字处理器中的撤消/重做功能、表达式求值和语法分析、语言处理、支持递归,我们自己的 JVM 也是面向堆栈的。因此,让我们深入了解一下如何编写无锁堆栈。希望它能点燃你的心,让你进一步阅读并获得这方面的知识。

Java 中的原子类

Java 提供了大量支持无锁和线程安全编程的类。Java 提供的原子 API,Java . util . concurrent . Atomic包包含许多高级类和特性,无需使用锁即可提供并发控制。 原子引用 也是应用编程接口中的一个这样的类,它提供了对可以原子读写的底层对象引用的引用。原子的意思是,对这些变量的读写是线程安全的。详情请参考以下链接。

CAS Inside–CompareAndSwap 操作:

最重要的操作是比较和交换,它是无锁算法的基本构件。它编译成单个硬件操作,这使得同步在粒度级别上更快。此外,该操作在所有原子类中都可用。CAS 旨在通过与当前值进行比较来更新变量/引用的值。

为非阻塞堆栈应用 CAS:

非阻塞堆栈基本上意味着堆栈的操作对所有线程都可用,并且没有线程被阻塞。为了在堆栈操作中使用 CAS,编写了一个循环,其中使用 CAS 检查堆栈顶部节点(称为堆栈顶部)的值。如果 stackTop 的值如预期的那样,它将被替换为新的顶值,否则什么都不会改变,线程将再次进入循环。

假设我们有一个整数堆栈。假设 thread1 想在栈顶值为 90 时将值 77 推送到栈上。thread2 想要弹出堆栈的顶部,目前是 90。如果线程 1 试图访问堆栈,并且因为当时没有其他线程访问而被授予访问权限,那么线程首先获得堆栈顶部的最新值。然后,它进入 CAS 循环,用期望值检查堆栈顶部(90)。如果两个值相同,即:CAS 返回 true,这意味着没有其他线程修改它,新值(在我们的例子中是 77)被推送到堆栈上。而 77 成为新的栈顶。同时,线程 2 继续循环 CAS,直到 CAS 返回 true,以便从堆栈顶部弹出一个项目。这是下图。

Applying CAS for a Non-Blocking Stack

非阻塞堆栈的代码示例:

堆栈代码示例如下所示。在本例中,定义了两个堆栈。一个使用传统同步(命名为经典堆栈这里 ) 来实现并发控制。另一个堆栈使用 原子引用 类的比较和设置操作来建立无锁算法(这里命名为无锁堆栈)。这里,我们正在计算堆栈在 1/2 秒的跨度内执行的操作数。我们比较了以下两种堆栈的性能:

Java 语言(一种计算机语言,尤用于创建网站)


// Java program to demonstrate Lock-Free
// Stack implementation
import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
class GFG {
    public static void main(String[] args)
        throws InterruptedException
    {
        // Defining two stacks
        // Uncomment the following line to see the
        // standard stack implementation.
        // ClassicStack operStack = new
        // ClassicStack(); Lock-Free Stack
        // definition.
        LockFreeStack<Integer> operStack
            = new LockFreeStack<Integer>();
        Random randomIntegerGenerator = new Random();
        for (int j = 0; j < 10; j++) {
            operStack.push(Integer.valueOf(
                randomIntegerGenerator.nextInt()));
        }
        // Defining threads for Stack Operations
        List<Thread> threads = new ArrayList<Thread>();
        int stackPushThreads = 2;
        int stackPopThreads = 2;
        for (int k = 0; k < stackPushThreads; k++) {
            Thread pushThread = new Thread(() -> {
                System.out.println("Pushing into stack...");
                while (true) {
                    operStack.push(Integer.valueOf(
                        randomIntegerGenerator.nextInt()));
                }
            });
            // making the threads low priority before
            // starting them
            pushThread.setDaemon(true);
            threads.add(pushThread);
        }
        for (int k = 0; k < stackPopThreads; k++) {
            Thread popThread = new Thread(() -> {
                System.out.println(
                    "Popping from stack ...");
                while (true) {
                    operStack.pop();
                }
            });
            popThread.setDaemon(true);
            threads.add(popThread);
        }
        for (Thread thread : threads) {
            thread.start();
        }
        Thread.sleep(500);
        System.out.println(
            "The number of stack operations performed in 1/2 a second-->"
            + operStack.getNoOfOperations());
    }
    // Class defining the implementation of Lock Free Stack
    private static class LockFreeStack<T> {
        // Defining the stack nodes as Atomic Reference
        private AtomicReference<StackNode<T> > headNode
            = new AtomicReference<StackNode<T> >();
        private AtomicInteger noOfOperations
            = new AtomicInteger(0);
        public int getNoOfOperations()
        {
            return noOfOperations.get();
        }
        // Push operation
        public void push(T value)
        {
            StackNode<T> newHead = new StackNode<T>(value);
            // CAS loop defined
            while (true) {
                StackNode<T> currentHeadNode
                    = headNode.get();
                newHead.next = currentHeadNode;
                // perform CAS operation before setting new
                // value
                if (headNode.compareAndSet(currentHeadNode,
                                           newHead)) {
                    break;
                }
                else {
                    // waiting for a nanosecond
                    LockSupport.parkNanos(1);
                }
            }
            // getting the value atomically
            noOfOperations.incrementAndGet();
        }
        // Pop function
        public T pop()
        {
            StackNode<T> currentHeadNode = headNode.get();
            // CAS loop defined
            while (currentHeadNode != null) {
                StackNode<T> newHead = currentHeadNode.next;
                if (headNode.compareAndSet(currentHeadNode,
                                           newHead)) {
                    break;
                }
                else {
                    // waiting for a nanosecond
                    LockSupport.parkNanos(1);
                    currentHeadNode = headNode.get();
                }
            }
            noOfOperations.incrementAndGet();
            return currentHeadNode != null
                ? currentHeadNode.value
                : null;
        }
    }
    // Class defining the implementation
    // of a Standard stack for concurrency
    private static class ClassicStack<T> {
        private StackNode<T> headNode;
        private int noOfOperations;
        // Synchronizing the operations
        // for concurrency control
        public synchronized int getNoOfOperations()
        {
            return noOfOperations;
        }
        public synchronized void push(T number)
        {
            StackNode<T> newNode = new StackNode<T>(number);
            newNode.next = headNode;
            headNode = newNode;
            noOfOperations++;
        }
        public synchronized T pop()
        {
            if (headNode == null)
                return null;
            else {
                T val = headNode.getValue();
                StackNode<T> newHead = headNode.next;
                headNode.next = newHead;
                noOfOperations++;
                return val;
            }
        }
    }
    private static class StackNode<T> {
        T value;
        StackNode<T> next;
        StackNode(T value) { this.value = value; }
        public T getValue() { return this.value; }
    }
}

输出:

Pushing into stack...
Pushing into stack...
Popping from stack ...
Popping from stack ...
The number of stack operations performed in 1/2 a second-->28514750

上面的输出是通过实现无锁堆栈数据结构获得的。我们看到有 4 个不同的线程,2 个用于推动,2 个用于从栈弹出。操作数量意味着堆栈上的弹出或推送操作。
为了与使用传统同步实现并发的标准堆栈版本进行比较,我们可以取消第一行代码的注释,并对第二行代码进行如下注释。

Java 语言(一种计算机语言,尤用于创建网站)


// Lock Based Stack programming
// This will invoke the lock-based version of the stack.
import java.io.*;
class GFG {
    public static void main(String[] args)
    {
        ClassicStack<Integer> operStack = new ClassicStack<Integer>();
        // LockFreeStack operStack = new LockFreeStack();
    }
}

基于锁的堆栈的输出如下。它清楚地表明,无锁实现(上图)提供了几乎 3 倍多的输出。

输出:

Pushing into stack...
Pushing into stack...
Popping from stack ...
Popping from stack ...
The number of stack operations performed in 1/2 a second-->8055597

尽管无锁编程提供了无数的好处,但正确编程并不是一件小事。

优点:


  • 真正的无锁编程。

  • 死锁预防。

  • 更高的吞吐量。

cons


  • A-B-A 问题仍然可能发生在无锁算法中(这是一个变量的值从 A 变为 B,然后回到 A,而两个线程正在读取相同的值 A,而另一个线程却不知道)

  • 无锁算法可能并不总是很容易编码。

在 Java 世界中,无锁算法和数据结构是一个备受争议的话题。当使用基于锁或无锁的算法时,必须彻底了解系统。一个人必须非常注意使用它们中的任何一个。对于不同类型的并发问题,没有“一刀切”的解决方案或算法。因此,决定什么算法最适合某种情况,是多线程世界中编程的关键部分。

参考文献:


  • 并发原子包-汇总

  • 锁定支持

  • driver _ stack

  • 非阻塞算法介绍


推荐阅读
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • C++ 开发实战:实用技巧与经验分享
    C++ 开发实战:实用技巧与经验分享 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • Eclipse中解决JDK源码断点调试失效的问题 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 初探性能优化:入门指南与实践技巧
    在编程领域,常有“尚未精通编码便急于优化”的声音。为了从性能优化的角度提升代码质量,本文将带领读者初步探索性能优化的基本概念与实践技巧。即使程序看似运行良好,数据处理效率仍有待提高,通过系统学习性能优化,能够帮助开发者编写更加高效、稳定的代码。文章不仅介绍了性能优化的基础知识,还提供了实用的调优方法和工具,帮助读者在实际项目中应用这些技术。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 字节跳动深圳研发中心安全业务团队正在火热招募人才! ... [详细]
  • 如果程序使用Go语言编写并涉及单向或双向TLS认证,可能会遭受CPU拒绝服务攻击(DoS)。本文深入分析了CVE-2018-16875漏洞,探讨其成因、影响及防范措施,为开发者提供全面的安全指导。 ... [详细]
author-avatar
彬哥唔系我
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有