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

深入解析JDK1.8中的并发新功能与优化

JDK1.8引入了多项并发新特性,显著提升了编程效率。本文重点探讨了LongAdder和StampedLock的特性和应用场景。此外,还介绍了在多线程环境中发生死锁时,如何通过jps命令进行诊断和排查,提供了详细的步骤和示例。这些改进不仅增强了系统的性能,还简化了开发者的调试工作。

JDK1.8中有一些并发的新特性,可以提高变成的效率。本文写的主要是LongAdder和stampedlock的特性。
多线程发生死锁时dump查看方式:
使用命令jps:如下所示
这里写图片描述

通过这个命令我们可以得到死锁号,然后再通过命令jstack查看

如下所示:
这里写图片描述

LongAdder

LongAdder是什么?
在大数据处理过程,为了方便监控,需要统计数据,少不了原子计数器。为了尽量优化性能,需要采用高效的原子计数器。在jdk8中,引入了LongAdder,非常适合多线程原子计数器。
我们知道,AtomicLong已经是非常高效的了,涉及并发的地方都是使用CAS(无锁)操作,在硬件层次上去做 compare and set操作。效率非常高。
LongAdder比AtomicLong更加高效。

实现原理:
LongAdder 沿用了concurrentMap原理,他是将1个整数拆分成一个数组cells,数组中有若干个cell。若有多个线层,每个线程通过CAS更新其中的一个小cell。然后内部将数组做sum求和操作得到整数的value;
这样就使得AtomicLong的单一线程做CAS操作演变成多个线程同时做CAS操作,期间互不影响。从而提高效率;
LongAdder开始并没有做拆分,当多线程间执行遇到冲突时才会拆分cell,若是多线程执行始终没有冲突,则它相当于AtomicLong;

如何分配cell的???
拿到线程相关的HashCode对象后,获取它的code变量,计算出一个在Cells 数组中当前线程的HashCode对应的索引位置,并将该位置的Cell 对象拿出来用CAS更新它的value值。

LongAdder的继承树
这里写图片描述

LongAdder的方法
这里写图片描述

使用案例

import java.util.concurrent.atomic.LongAdder;

public class LongAdderTest {

    private static LongAdder la =new LongAdder();

    public static int a =0;
    public static void add(){
        la.increment();
        a++;
    }
    /** * @param args * @throws InterruptedException */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                for(int i=0;i<10000;i++){
                    add();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                for(int i=0;i<10000;i++){
                    add();
                }
            }
        });
        t2.start();
        t1.join();t2.join();
        System.out.println("---la-----"+la);
        System.out.println("---a-----"+a);
    }

}

执行结果:
这里写图片描述

StampedLock

stampedLock推出了乐观读锁,在使用乐观读锁时,不会阻塞写锁,这使得我们在写数据时,不会因为使用读锁而长时间的阻塞写,从而提高效率;
ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。
然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程迟迟无法竞争到锁定而一直处于等待状态。
StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!
示例代码:

import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {

    private static final StampedLock sl = new StampedLock();
    private static int x;
    private static int y;

    public static void move(int deltax,int deltay) throws InterruptedException{
        System.out.println("写线程----"+Thread.currentThread().getName());
        long sw = sl.writeLock();//获取写锁
        try{
            x = x+deltax;
            y = y+deltay;
        }finally{
            sl.unlockWrite(sw);//释放写锁
        }
    }

    public static int distanceFromOrigin(){
        long sr = sl.tryOptimisticRead(); //获取乐观读锁,不会阻塞写锁
        int currentx = x;
        int currenty = y;
        //读完成后验证期间是否有写操作改变了数据sl.validate(sr)为true则表示期间无写操作,否则表示数据可能已经被改变
        System.out.println(currentx+"---第一次读取数据-----"+currenty);
        if(!sl.validate(sr)){
            sr = sl.readLock(); //使用了悲观锁,会阻塞写锁
            try{
                System.out.println("悲观锁读线程----------"+Thread.currentThread().getName());
                currentx = x;
                currenty = y;
            }finally{
                sl.unlockRead(sr);//释放悲观读锁
            }
        }else{
            System.out.println("乐观锁读线程----------"+Thread.currentThread().getName());
        }
        System.out.println(currentx+"----第二次读取数据----"+currenty);
        return currentx*currenty;
    }
    /** * @param args */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for(int i=0;i<10;i++){
            final int q =i;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        move(q,q+8);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        for(int i=0;i<50;i++){
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        distanceFromOrigin();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

}

这里写图片描述


推荐阅读
  • 【并发编程】全面解析 Java 内存模型,一篇文章带你彻底掌握
    本文深入解析了 Java 内存模型(JMM),从基础概念到高级特性进行全面讲解,帮助读者彻底掌握 JMM 的核心原理和应用技巧。通过详细分析内存可见性、原子性和有序性等问题,结合实际代码示例,使开发者能够更好地理解和优化多线程并发程序。 ... [详细]
  • 题目:图像处理(HDU1828,计算周长并集,利用线段树与离散化技术进行扫描) ... [详细]
  • 2021年7月22日上午9点至中午12点,我专注于Java的学习,重点补充了之前在视频中遗漏的多线程知识。首先,我了解了进程的概念,即程序在内存中运行时形成的一个独立执行单元。其次,学习了线程作为进程的组成部分,是进程中可并发执行的最小单位,负责处理具体的任务。此外,我还深入研究了Runnable接口的使用方法及其在多线程编程中的重要作用。 ... [详细]
  • Java集合框架特性详解与开发实践笔记
    Java集合框架特性详解与开发实践笔记 ... [详细]
  • 如何在 IntelliJ IDEA 中高效搭建和运行 Spring Boot 项目
    本文详细介绍了如何在 IntelliJ IDEA 中高效搭建和运行 Spring Boot 项目,涵盖了项目创建、配置及常见问题的解决方案。通过本指南,开发者可以快速掌握在 IntelliJ IDEA 中进行 Spring Boot 开发的最佳实践,提高开发效率。 ... [详细]
  • 利用Java开发功能完备的电话簿应用程序,支持添加、查询与删除操作
    本研究基于Java语言开发了一款功能全面的电话簿应用程序,实现了与数据库的高效连接。该应用不仅支持添加、查询和删除联系人信息,还具备输出最大和最小ID号的功能,并能够对用户输入的ID号进行有效性验证,确保数据的准确性和完整性。详细实现方法可参阅相关文档。 ... [详细]
  • 通过整合JavaFX与Swing,我们成功地将现有的Swing应用程序组件进行了现代化改造。此次升级不仅提升了用户界面的美观性和交互性,还确保了与原有Swing应用程序的无缝集成,为开发高质量的Java桌面应用提供了坚实的基础。 ... [详细]
  • 本文详细探讨了Java中Unicode编码的二进制转换方法及其具体实现。通过分析\u开头的字符串,解释了每组\uxxxx如何对应一个特定的Unicode字符,并提供了相关代码示例以加深理解。希望读者在实际开发中能有效应用这些知识。 ... [详细]
  • 本文作为《Java学习笔记》的开篇,旨在为初学者提供一个全面的概览。文章首先介绍了Java的基本概念及其在编程语言中的地位,强调了Java与其他主流编程语言的共通之处,特别是其核心结构,如控制语句的重要性。通过详细的目录和前言,读者可以快速了解Java的基础知识和学习路径。此外,文章还探讨了控制语句在编程中的关键作用,为后续深入学习打下坚实基础。 ... [详细]
  • 在Python中,通过实现一个便捷的函数来解码Base64编码的数据,并将其转换为数组形式。该函数能够将Base64字符串解码为字节数组,便于进一步处理。例如,可以使用如下代码片段进行解码:`base64_decode_array('6gAAAOsAAAD')`。这为处理二进制数据提供了高效且简洁的方法。 ... [详细]
  • 本文详细探讨了OpenCV中人脸检测算法的实现原理与代码结构。通过分析核心函数和关键步骤,揭示了OpenCV如何高效地进行人脸检测。文章不仅提供了代码示例,还深入解释了算法背后的数学模型和优化技巧,为开发者提供了全面的理解和实用的参考。 ... [详细]
  • Pentaho Data Integration 中 BaseStep 类 getOutputRowSets 方法详解与代码实例 ... [详细]
  • 本书详细介绍了在最新Linux 4.0内核环境下进行Java与Linux设备驱动开发的全面指南。内容涵盖设备驱动的基本概念、开发环境的搭建、操作系统对设备驱动的影响以及具体开发步骤和技巧。通过丰富的实例和深入的技术解析,帮助读者掌握设备驱动开发的核心技术和最佳实践。 ... [详细]
  • 在Java中,使用`java.awt.Frame`类可以轻松创建窗口,并通过简单的方法设置窗口标题。本文详细介绍了如何在自定义的`BallGame`类中扩展`Frame`类,并实现窗口的创建与标题设置。通过示例代码展示了具体实现步骤,帮助开发者快速掌握这一基础技能。 ... [详细]
  • 如何使用 net.sf.extjwnl.data.Word 类及其代码示例详解 ... [详细]
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社区 版权所有