广州软件公司最有名气的是哪几家

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了广州软件公司最有名气的是哪几家相关的知识,希望对你有一定的参考价值。

广州软件公司最有名气的是哪几家介绍下、

广州碧软信息科技有限公司
成立于2010年10月14日,位于华师地铁B口的高科大厦办公。经营范围包括电子、通信与自动控制技术研究、开发;信息技术咨询服务;数字动漫制作;信息系统集成服务;计算机技术开发、技术服务。

2.亿达信息技术有限公司
亿达信息技术有限公司(简称YIDATEC或亿达信息)创建于2006年,是中国领先、国际化的IT服务及数字化运营专家,致力于在数字化时代助力企业实现数据驱动的产品和服务。

3.广州市网威信息技术有限公司
广州市网威信息技术有限公司成立于2006年03月09日。经营范围包括软件开发;网络与信息安全软件开发;网络技术服务;信息技术咨询服务;信息系统集成服务;动漫游戏开发;技术服务、技术开发、技术咨询、技术交流、技术转让、技术推广。

4.广汽汇理汽车金融有限公司
广汽汇理汽车金融有限公司(简称广汽汇理)系广州汽车集团股份有限公司和东方汇理个人金融股份有限公司分别以股比50%的形式合资成立的汽车金融公司。广汽汇理成立于2010年5月25日,是广东省首家汽车金融公司。公司主营业务是为广汽集团相关品牌的汽车终端客户和经销商提供汽车金融相关的软件开发任务。
参考技术A 软件开发公司排行榜
极其流行,同样也是竞争力极其大的一种商业模式。虽然国内软件开发公司都发展壮大起来了,但是各地软件开发公司的实力及资质仍然参差不齐。下面为大家介绍下近期国内软件开发公司的排名汇总。
1:华盛恒辉科技有限公司
上榜理由:华盛恒辉是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在开发、建设到运营推广领域拥有丰富经验,我们通过建立对目标客户和用户行为的分析,整合高质量设计和极其新技术,为您打造创意十足、有价值的企业品牌。
在军工领域,合作客户包括:中央军委联合参谋(原总参)、中央军委后勤保障部(原总后)、中央军委装备发展部(原总装)、装备研究所、战略支援、军事科学院、研究所、航天科工集团、中国航天科技集团、中国船舶工业集团、中国船舶重工集团、第一研究所、训练器材所、装备技术研究所等单位。
在民用领域,公司大力拓展民用市场,目前合作的客户包括中国中铁电气化局集团、中国铁道科学研究院、济南机务段、东莞轨道交通公司、京港地铁、中国国电集团、电力科学研究院、水利部、国家发改委、中信银行、华为公司等大型客户。
2:五木恒润科技有限公司
上榜理由:五木恒润拥有员工300多人,技术人员占90%以上,是一家专业的军工信息化建设服务单位,为军工单位提供完整的信息化解决方案。公司设有股东会、董事会、监事会、工会等上层机构,同时设置总经理职位,由总经理管理公司的具体事务。公司下设有研发部、质量部、市场部、财务部、人事部等机构。公司下辖成都研发中心、西安研发中心、沈阳办事处、天津办事处等分支机构。

3、浪潮
浪潮集团有限公司是国家首批认定的规划布局内的重点软件企业,中国著名的企业管理软件、分行业ERP及服务供应商,在咨询服务、IT规划、软件及解决方案等方面具有强大的优势,形成了以浪潮ERP系列产品PS、GS、GSP三大主要产品。是目前中国高端企业管理软件领跑者、中国企业管理软件技术领先者、中国最大的行业ERP与集团管理软件供应商、国内服务满意度最高的管理软件企业。
4、德格Dagle
德格智能SaaS软件管理系统自德国工业4.0,并且结合国内工厂行业现状而打造的一款工厂智能化信息平台管理软件,具备工厂ERP管理、SCRM客户关系管理、BPM业务流程管理、
OMS订单管理等四大企业业务信息系统,不仅满足企业对生产进行简易管理的需求,并突破局域网应用的局限性,同时使数据管理延伸到互联网与移动商务,不论是内部的管理应用还是外部的移动应用,都可以在智能SaaS软件管理系统中进行业务流程的管控。
5、Manage
高亚的产品 (8Manage) 是美国经验中国研发的企业管理软件,整个系统架构基于移动互联网和一体化管理设计而成,其源代码编写采用的是最为广泛应用的
Java / J2EE 开发语言,这样的技术优势使 8Manage
可灵活地按需进行客制化,并且非常适用于移动互联网的业务直通式处理,让用户可以随时随地通过手机apps进行实时沟通与交易。
参考技术B 请问你说的是指那方面的软件?
我这里有份ERP软件的排名:2006年十大领域管理软件供应商入选厂商如下(排名不分先后)ERP领域Top 10:
(国内组)用友、金蝶、傲鹏、神州数码、浪潮、新中大、天心耀华、明基逐鹿、金算盘、正航软件(国际组)SAP、Oracle、Infor、Sage、QAD、CDC software、Softbrands、IFS、Epicor、Exact
参考技术C

我做展览的,展览公司软件我就知道,至简科技的展览软件可以,他们是第一家做展览软件的.别的就不清楚了

本回答被提问者采纳
参考技术D 广州的软件公司如( 天翎、宏天、红迅、联科等)

中针对提供平台源码,开放程度各自有差异:
1、提供平台源码和业务全源码的很多家:天翎、红讯、宏天,但是各自的还有细节细分:譬如宏天只允许部署不超过10次,有些厂家不限制部署次数,譬如天翎;
2、有些是针对部分授权或接口授权,既然都控制了部署的层面的要求,或用户数要求,如何约定对应的部署呢?
3、如果预算是60万,建议尝试下天翎的集团版,不限制用户数和软件个数,提供除了版权所有的标志的封装,其他的全部PC移动的全部源码。其实针对源码我个人理解,不是一定要占有别人劳动成果,关键是针对很多开发不确定因素,通过这样的方式,可以实现拓展和领域应用,再编译更加方便和便捷。
听说合作方式也是多元化:
A、购买平台自主开发,B、共同开发,C、外包项目的合作
其实都在广州,建议直接约过去企业,做个沟通和展示,经过一轮比较,应该可以找到适合的产品,期待你的选型可以精准,不会再走弯路啦。

2-JDK源码对你最有触动的是哪一段#工具类

目录

工具类

WeakReference的使用场景

ServiceLoader

Lock

IntegerCache

位运算

ReentrantReadWriteLock的实现

LongAdder

AbstractQueuedSynchronizer

ThreadLocal基于数组结构的拉链法存储


工具类

WeakReference的使用场景

WeakReference (弱引用) ,用来描述非必须存在的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

一直很好奇这个类会在什么时候使用,看这个类的描述,我们在业务开发中好像很少甚至基本没有机会使用这个类,打个比方我们在方法中新建一个对象 A a = new A() 我们不可能写成 WeakReference<A> a = new WeakReference<>(new A()) 这样, 因为这是个弱引用,

万一我方法没执行完成,GC生效了,把我这个对象回收了,接着执行获取到的是个null对象,就出乱子了。

直到有天看到了ThreadLocal中使用了WeakReference,

ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("1");

先看上面两行代码会发生什么事情 ,例如当前线程名称叫做A,

ThreadLocal.ThreadLocalMap threadLocals = null;

A线程对象内部有一个 名叫threadLocals 的 ThreadLocal.ThreadLocalMap 类型对象。

ThreadLocal.ThreadLocalMap 对象中有一个类型为 ThreadLocal.ThreadLocalMap.Entry 对象数组,

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
​
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    private Entry[] table;

当我们执行 threadLocal.set("1"); 这行代码的时候, 这个方法会判断当前线程的 ThreadLocalMap 中有没有 key 和当前 threadLocal 对象相等的 Entry ,

如果有则替换Entry对象的value,如果没有,就新建一个key为 threadLocal 对象的 Entry 添加到 线程的ThreadLocalMap 中去,

这里的Entry就是弱引用,为什么这么设置?我们上面说了 一个 Entry 就保存着一个 ThreadLocal 对象,假设Entry是强引用对象,我们现在有这么一段需求,

例如 threadLocal = null; , 我们把这个对象引用置为null,希望下次GC把threadLocal 引用的对象一起回收掉,此刻虽然threadLocal 引用已经被置为空了,但是它之前引用的对象能被回收掉吗?

不可以,因为虽然threadLocal 是 null 了,但是还有ThreadLocalMap 中的 Entry 在引用着这个实际对象,导致GC无法正常回收到,

这显然不是我们想要看到的,如果这个线程一直存在的话,不停的在线程的方法栈中创建使用ThreadLocal threadLocal ,

然后这个变量threadLocal虽然会随着栈帧销毁也没有,但是它指向的对象却因为还有Entry在引用着,一直不回收,所以一直存在堆中,造成了内存泄漏,

所以jdk为了避免这种情况,将Entry继承了WeakReference类,在适当的情况下,让垃圾对象被回收,很优雅的做法。

ThreadLocal.ThreadLocalMap threadLocals 这个属性为什么要放到Thread中呢? 我认为jdk想的是,这样这个对象就可以随线程生,随线程亡了。

ServiceLoader

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

之前在刚使用spring boot时,只是感受到了spring boot的便捷,也听到同事说和SPI有关,但一直不知道SPI是什么,在一次分析JDBC连接异常时,进到JDBC的源码,理解了SPI的思想。

ServiceLoader.load(Driver.class) 通过内部类LazyIterator 将所有数据库驱动jar包下META-INF/services/ 目录中以Driver命名的文件中的实现类进行加载,将加载的驱动信息放到 CopyOnWriteArrayList 中, 在getConnection 时通过传入的URL等信息进行遍历 获取连接;Driver.java 为驱动接口,OracleDriver.java,DruidDriver.java等是它的实现,这种调用方来制定接口规范,提供给外部来实现,调用方在调用时选择自己需要的外部实现的思想,让我对低耦合有了更深刻的认识;同时也对spring boot 中spi的思想有了近一步的认识,通过扩展我还知道了dubbo中也使用了spi的思想,接下来通过这条线看看dubbo中spi的思想运用。

Lock

在我看 JDK 源码的过程中,最有触动的一段是关于 Lock 锁的注释,在 Lock 与 Condition 的接口中,四五百行的文件中基本都是注释,首先讲解了锁的一般概念,然后是与 synchornized 的比较,以及使用的最佳实践。 把 Lock 锁与 synchronized 在使用中的区别将清楚了:

Lock 有更灵活的结构;

Lock 提供了更多特性(其实就是提供的几个方法),比如能够响应中断,支持超时,非阻塞地获取锁;

Lock 支持多个 condition

而且将 Lock 锁的最佳用法写在了注释中:

 Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
 } finally {
   l.unlock();
 }

之后在阿里巴巴的 Java 开发手册中也看到了对 Lock 锁使用的规范,其实就是 JDK 中的注释所写的代码:在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁;以及对这个规范的三个说明。

当初学习 Lock 时看到如此详细的注释,感觉很震感,因为很多概念,用法,甚至最佳实践,可能就存在于JDK 中源码的注释信息中;之后发现 JDK 源码中的注释真的是学习的第一手的资料,但是 Lock 这个真的是我记忆中影响最深的一次了。

IntegerCache

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
​
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
​
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
​
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
​
        private IntegerCache() {}
    }

缓存以支持 JLS 要求的 -128 和 127(含)之间值的自动装箱的对象标识语义。 缓存在第一次使用时初始化。 缓存的大小可以由-XX:AutoBoxCacheMax=<size>选项控制。 在VM初始化过程中,java.lang.Integer.IntegerCache.high属性可能会被设置并保存在sun.misc.VM类的私有系统属性中。

【简述】 Jdk1.5中引入了Integer对象的cache机制。最初是因为一道面试题踩了坑,后来仔细看了Integer类才知道这个IntegerCache。这段代码并不难理解,其它的包装类也同时引入了缓存机制,最触动我的地方在于,优秀的框架和源码都在不断地从实践中追求性能和极致,我们应该用怎样的态度对待我们手里的代码呢?

【分析】 按照正常的思维,需要装箱时这里直接new一个对象就好。但这里给定了一个默认范围,[-128,127]内的数字进行装箱时首先会去IntegerCache维护的一个cache[]中获取,没有的话再new一个

这样的好处在于如果有高频的转换,这里的缓存将会节省可观的内存,并加快响应速度。在Integer.valueOf(int i)方法的注释中对此说明:该方法为了更好地能缓存了频繁使用的值(frequently requested values)。

这里引起我兴趣的是这个默认范围的设定[-128,127] 在IntegerCache类的注释中,有写到:Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS. 这里查阅了资料,最终的结论是: 1.JSL中约定的是-128到127,这是至少要缓存的范围。可以缓存更大的范围,但是[-128,127]是必须的。理想情况下,给定的数值所产生的引用应该总是相同的,但实际上不可能办到。所以设计者给定一个范围,要求至少在[-128,127]是必须要缓存的。 2.这个范围,可以覆盖大多数常见的情况(most common cases),保证相同数值对应相同的引用,而不会施加不当的性能损失,尤其是在小型设备,节约出来的内存将是非常有用的。

【启发】 这段代码其实在当时给了我很大的启发。之前所认为的代码优化,总是会去想一些高大上的技术,但是具体怎么落实并没有思路。但是这段代码带给我的启发是,只要是着力点找到了,用一些简单的手法我们也能有效提升代码的性能,以至于后来的开发工作中,如果某些对象使用很高频,会有意识去维护一个数组,或者集合提升对象的复用,我觉得这也算是看代码带来的一些处理思路上的改进。

记得有道面试题类似下面的,问输出是什么。

public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}

没看过源码之前,很想当然的认为两个都是true了。结果匪夷所思为什么c和d就不相等,通过查看源码之后才发现,内部另有玄机。 原因是从JDK1.5之后,Integer 把 -128~127的数字缓存了起来,在自动装箱时调用valueOf(int i) 方法,源码如下,

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

备注

  • 如果==比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等;

  • 如果==是比较的两个对象,那么比较的是两个对象的引用,也就是两个对象是否为同一个对象,并不是比较的对象的内容;

范围在-128~127之间的数都通过IntegerCache来获取。超出范围的则新建Integer对象。

所以a和b的值都是IntegerCache.cache[129] = 1, 而c 和 d 超出了cache范围,创建了两个值为128的对象,所以对象c和对象d是不相同的的两个对象。

自从第一次开始读起源码,才发现Java代码是如此的优雅,考虑到经常用的东西就把它缓存起来,提高性能。这种思想值得借鉴。 好比现在为什么要用redis等缓存机制,同样的道理是为了提供便捷,提高性能。

位运算

ThreadPoolExecutor 中状态定义和线程数的设置:

/**
     * The main pool control state, ctl, is an atomic integer packing
     * two conceptual fields
     *   workerCount, indicating the effective number of threads
     *   runState,    indicating whether running, shutting down etc
     *
     * In order to pack them into one int, we limit workerCount to
     * (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
     * billion) otherwise representable. If this is ever an issue in
     * the future, the variable can be changed to be an AtomicLong,
     * and the shift/mask constants below adjusted. But until the need
     * arises, this code is a bit faster and simpler using an int.
     *
     * The workerCount is the number of workers that have been
     * permitted to start and not permitted to stop.  The value may be
     * transiently different from the actual number of live threads,
     * for example when a ThreadFactory fails to create a thread when
     * asked, and when exiting threads are still performing
     * bookkeeping before terminating. The user-visible pool size is
     * reported as the current size of the workers set.
     *
     * The runState provides the main lifecycle control, taking on values:
     *
     *   RUNNING:  Accept new tasks and process queued tasks
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed
     *
     * The numerical order among these values matters, to allow
     * ordered comparisons. The runState monotonically increases over
     * time, but need not hit each state. The transitions are:
     *
     * RUNNING -> SHUTDOWN
     *    On invocation of shutdown(), perhaps implicitly in finalize()
     * (RUNNING or SHUTDOWN) -> STOP
     *    On invocation of shutdownNow()
     * SHUTDOWN -> TIDYING
     *    When both queue and pool are empty
     * STOP -> TIDYING
     *    When pool is empty
     * TIDYING -> TERMINATED
     *    When the terminated() hook method has completed
     *
     * Threads waiting in awaitTermination() will return when the
     * state reaches TERMINATED.
     *
     * Detecting the transition from SHUTDOWN to TIDYING is less
     * straightforward than you'd like because the queue may become
     * empty after non-empty and vice versa during SHUTDOWN state, but
     * we can only terminate if, after seeing that it is empty, we see
     * that workerCount is 0 (which sometimes entails a recheck -- see
     * below).
     */    
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
​
// runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
​
    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

主池控制状态ctl是一个原子整数,封装了两个概念字段workerCount,表示有效线程数runState,表示是否正在运行、正在关闭等为了打包成一个int,我们限制workerCount为(2^29 )-1(约 5 亿)个线程而不是 (2^31)-1(20 亿)个其他可表示的线程。

如果这在未来成为一个问题,可以将变量更改为 AtomicLong,并调整下面的移位/掩码常量。 但是在需要之前,这段代码使用 int 会更快更简单。 workerCount 是允许启动和不允许停止的工人数量。

该值可能与实际的活动线程数暂时不同,例如当 ThreadFactory 在被询问时未能创建线程时,以及退出线程在终止前仍在执行簿记时。 用户可见的池大小报告为工作人员集的当前大小。

runState 提供主要的生命周期控制,取值:

  • RUNNING:接受新任务并处理排队任务

  • SHUTDOWN:不接受新任务,但处理排队任务

  • STOP:不接受新任务,不处理排队任务,并中断正在进行的任务

  • TIDYING:所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法

  • TERMINATED: terminate() 已完成这些值之间的数字顺序很重要,以允许有序比较. runState 随时间单调增加,但不需要命中每个状态。

转换是:

RUNNING -> SHUTDOWN

在调用 shutdown() 时,可能隐含在 finalize() 中

(RUNNING 或 SHUTDOWN)-> STOP

在调用 shutdownNow() 时

SHUTDOWN -> TIDYING 当队列和池都为空时

STOP -> TIDYING当池为空时

TIDYING -> TERMINATED

当 terminate() 钩子方法完成时,

在 awaitTermination() 中等待的线程将在状态达到 TERMINATED 时返回。

检测从 SHUTDOWN 到 TIDYING 的转换并不像您想要的那么直接,因为在非空之后队列可能会变空,在 SHUTDOWN 状态期间反之亦然,但是我们只能在看到它为空后看到 workerCount 时才终止是 0

jdk 中的给我印象最深的代码是上面的代码,作者使用二进制的来定义线程的运行状态,如上面的使用了int 类型的值的高 3 位定义了线程的RUNNING,SHUTDOWN、STOP、TIDYING、TERMINATED状态。 后面低的 28 位为线程池可运行的最大运行数量。 同时也在注释中提供了不同状态之间转换代码和评断的当前的状态的方法。如下面注释:

 * RUNNING -> SHUTDOWN
 *    On invocation of shutdown(), perhaps implicitly in finalize()
 * (RUNNING or SHUTDOWN) -> STOP
 *    On invocation of shutdownNow()
 * SHUTDOWN -> TIDYING
 *    When both queue and pool are empty
 * STOP -> TIDYING
 *    When pool is empty
 * TIDYING -> TERMINATED
 *    When the terminated() hook method has completed

让读者在阅读的时候,可以快速理解各种状态之间状态和状态之间转换时使用的方法。

通过以上两点,可以知道

  1. 在自己程序定义程序的运行状态可以使用int 的高m位定义状态,同时低n位存储数量(32=m+n)。

  2. 使用位运算可以节省空间同时提升了代码的运行速度。

  3. 在不同状态的转换过程要提供封装好的方法,方便使用者可以快速了解并使用,避免其直接对细节处理出错,便于团队合作。

ReentrantReadWriteLock的实现

ReentrantReadWriteLock采用读写分离的策略来保证:1.只允许一个线程进行写入;2.没有写入时,多个线程可以读取(ReentrantReadWriteLock内部维护了ReadLock和WriteLock,ReadLock在获取锁时,会先判断WriteLock是否被其他线程占用,WriteLock在获取锁时,也会先判断WriteLock是否被其他线程占用)

理由:1.只用一个state就可以维护读写两种状态,state高16位表示获取读锁的次数(c >>> SHARED_SHIFT),低16位表示获取写锁的次数(c & EXCLUSIVE_MASK),再次领会到了位运算的妙用,以及用一个变量维护两种状态来减少维护成本的思想

2.ReentrantReadWriteLock相比于独占锁(synchronized,ReentrantLock)在读多写少的应用场景下,兼顾线程安全的同时,具有较好的性能,相较于以前了解的多线程分而治之、化整为零的思想,ReentrantReadWriteLock的处理思想拓宽了我对于多线程的视野,在构建一个读多写少场景下线程安全的List,或是协同编辑一份文件,ReentrantReadWriteLock的应用想必是如虎添翼

LongAdder

LongAdder 的主要功能是在并发环境下计数。在并发极高的环境下,性能表现优于 AtomicLong。

  
      public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // force initialization
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        //如果操作的cell 为空,double check 新建 cell
        if ((as = cells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {       // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    if (cellsBusy == 0 && casCellsBusy()) {
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            
        // cas 失败 继续循环
        else if (!wasUncontended)       // CAS already known to fail
            wasUncontended = true;      // Continue after rehash
​
        // 如果 cell cas 成功 break
        else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                     fn.applyAsLong(v, x))))
            break;
​
        // 如果 cell 的长度已经大于等于 cpu 的数量,扩容意义不大,就不用标记冲突,重试
        else if (n >= NCPU || cells != as)
            collide = false;            // At max size or stale
        else if (!collide)
            collide = true;
        // 获取锁,上锁扩容,将冲突标记为否,继续执行    
        else if (cellsBusy == 0 && casCellsBusy()) {
            try {
                if (cells == as) {      // Expand table unless stale
                    Cell[] rs = new Cell[n << 1];
                    for (int i = 0; i < n; ++i)
                        rs[i] = as[i];
                    cells = rs;
                }
            } finally {
                cellsBusy = 0;
            }
            collide = false;
            continue;                   // Retry with expanded table
        }
        // 没法获取锁,重散列,尝试其他槽
        h = advanceProbe(h);
    }
​
    // 获取锁,初始化 cell 数组
    else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
        boolean init = false;
        try {                           // Initialize table
            if (cells == as) {
                Cell[] rs = new Cell[2];
                rs[h & 1] = new Cell(x);
                cells = rs;
                init = true;
            }
        } finally {
            cellsBusy = 0;
        }
        if (init)
            break;
    }
​
    // 表未被初始化,可能正在初始化,回退使用 base。
    else if (casBase(v = base, ((fn == null) ? v + x :
                                fn.applyAsLong(v, x))))
        break;                          // Fall back on using base
}

原因: 实现的思想对于我们设计系统有很大的参考价值。 使用无锁的 cas 操作,提高并发表现。 使用分桶的操作,代码中的 cells 分散竞争。提升代码性能表现。 代码严谨在 cas 的相关操作中都加入了 double check 的操作。 不在一开始就提供系统的最大能力。而是逐步升级。保证代码性能。

看完代码,感叹作者实现的精巧和细致,使用分散竞争,逐步升级的思路解决了极高并发下的性能问题。

AbstractQueuedSynchronizer

jdk最让我触动是AbstractQueuedSynchronizer(抽象的队列同步器)的设计,它是一个公共的同步组件,常常惊叹其设计。其内部核心首先就是拥有了一个代表同步状态的共享变量 即:private volatile int state; 该变量用volatile修饰,保证多线程的内存可见性,它表示的是同步锁状态及数量 用简单的表达式表示就是:state>0?获取到了N个锁:已释放锁。 有了安全强大的共享资源,AQS并没有简单的采用synchronized关键字这种阻塞的方式来实现加锁/释放锁,因为这种方式会让线程都阻塞,导致线程上下文切换很费性能, 而且也不能实现tryLock这样的语义,另外这种方式很依赖JVM底层,说到底是一种限制,会导致很多个性化功能无法实现,比如公平锁和非公平锁。 AQS是通过CAS无锁算法实现的。

/**
​
 * 以CAS指令原子性的更新State变量,保证在多线程之间安全的更新此值
 * @param expect 期待的值
 * @param update 要更新的新的值
 * @return 如果成功返回true,如果事实的值不等于期待的值返回false
   */
   protected final boolean compareAndSetState(int expect, int update) {
   // See below for intrinsics setup to support this
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
   }
   // 这是一个本地方法通过CAS实现无锁的原子性的更新共享变量的值
   public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
​AQS采用上述的compareAndSwapInt CAS算法实现高效的更新共享变量。 其实有了state和安全高效的并发更新的方式就可以简单实现多线程的同步了,但AQS并不止于此。 真实的使用场景我们往往是希望线程在争夺同步资源失败后能够自己再重试,并且不要那么频繁的重试,最好是在锁被释放后再去竞争。为此,AQS内部维护一个同步队列, 会将获取同步状态失败的线程以及它当前的同步状态封装为一个Node将其加入同步队列的尾部。

 

 /**
 * 将当前线程加入到等待队列队尾,并返回当前线程所在节点.
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
   */
   private Node addWaiter(Node mode) {
   //创建节点以当前线程和给定模式为参数
   Node node = new Node(Thread.currentThread(), mode);
   //先尝试一下直接放到队尾,普通的链表操作.
   Node pred = tail;
   if (pred != null) {
       node.prev = pred;
       if (compareAndSetTail(pred, node)) {
           pred.next = node;
           return node;
       }
   }
   //尝试失败则通过enq入队。
   enq(node);
   return node;
   }
  

可以看到这段代码也用一个CAS的安全设置同步队列尾部的方法compareAndSetTail。节点加入到同步队列后,就会进入一个自旋竞争资源的过程,大概就是先拿到当前线程在队列的前驱节点,如果这个前驱节点恰好是头结点,自己就是下一个那么就尝试去获取资源,如果获取不到判断自己可以休息,就wating,等待被unpark继续上述流程。 AQS作为一个队列同步器框架,采用了模板设计模式,里面的tryAcquire、tryAcquireShare分别是独占模式和共享模式的扩展点,交由子类去实现的,决定能不能重入,怎样才算加锁成功,我们就可以很方便实现不同功能的同步锁,比如JDK已经实现的CountDownLatch、ReentrantLock等,当然我们在工作中有需要也可以定制实现满足特殊业务场景需求的锁。这也给我了震撼和很强的启发,我也因此坚信抽象能力是优秀程序员的一种必备能力。

 

ThreadLocal基于数组结构的拉链法存储

印象最深的是ThreadLocal基于数组结构的拉链法存储。因为面试的时候,面试官问到了,而当时我不知道,所以下来之后就自己看了一下。因为斐波那契散列的非常均匀,普通散列到15个以后已经开发生产碰撞。这也就是斐波那契散列的魅力,减少碰撞也就可以让数据存储的更加分散,获取数据的时间复杂度基本保持在O(1)。

//通常的hashcode
public int hashCode() {  
 int h = hash;  
    int len = count;  
 if (h == 0 && len > 0) {  
    int off = offset;  
    char val[] = value;  
           for (int i = 0; i < len; i++) {  
               h = 31*h + val[off++];  
           }  
           hash = h;  
       }  
       return h;  
} 
//基于斐波那契(Fibonacci)散列法 + 拉链法
private static final int HASH_INCREMENT = 0x61c88647; 
private static int nextHashCode() { 
    return nextHashCode.getAndAdd(HASH_INCREMENT); 
}
​
f(k) = ((k * 2654435769) >> X) << Y
//对于常见的32位整数而言,也就是 
  f(k) = (k * 2654435769) >> 28

以上是关于广州软件公司最有名气的是哪几家的主要内容,如果未能解决你的问题,请参考以下文章

网页三剑客是哪几个软件?分别是做啥的?

云南百度笔记排名比较靠前的有哪几家?云南最好的笔记公司是哪家?

2-JDK源码对你最有触动的是哪一段#工具类

2-JDK源码对你最有触动的是哪一段#HashMap

2-JDK源码对你最有触动的是哪一段#集合

两个 Activity 之间跳转时必然会执行的是哪几个方法?