精华内容
下载资源
问答
  • java中运算符优先级梳理

    千次阅读 2014-04-27 21:17:08
    老生常谈,运算符的优先级,除了右结合的运算符之外,同级d

    老生常谈,运算符的优先级,除了右结合的运算符之外,同级的运算符要按照从左到右的顺序依次计算。

    什么是右结合的运算符呢?

    经常使用的赋值=,以及派生出来的复合赋值运算符,都是从右到左的运算顺序,这就是右结合符号。不少参考书里都有个表,有的时候很多jb破书,炫耀很多一系列的运算符,弄一大堆在一个大……长串的表达式上,讲解这个顺序那个顺序的,jb毛线用处都没有,纯粹就是占据字数的sb,不解释,我把这些运算符的优先级梳理记忆和总结下;


    1、众所周知,一般情况下有括号()就是级别最高的!先算括号里的,比如;

    		System.out.println(2 + 2 / 2);
    		System.out.println((2 + 2) / 2);
    打印;3和2

    2、然后看表,最高级别的是数组下标【】、对象或者方法的调用 . 、方法的参数调用运算符(),这些都是从左到右的。

    3、记住一点,正负号,非!,按位取反~,自增,自减,强制类型转换,new这是一个级别的,从左到右。

    4、然后是最熟悉的加减乘除,按照数学的规则,先乘除后加减,同级的按照顺序,只不过这里多了个模运算%,和乘除一个级别的而已。也就是先乘除模,再加减而已。

    5、然后是移位的,左移,右移,无符号右移是一个级别的。

    6、关系运算符,也就是比较大小的,外加个instantof!一个级别的。

    7、最后的判等==,和不等!=,很好理解,都是先算了,再判等吧……结合常识。

    8、然后就是很好理解的;与>异或>或>双与>双或>三元,注意,三元运算符是从右到左的顺序。其余的是左到右。细细的感觉下,就是这样!

    9、最后就是赋值类的包括复合的,也是从右到座的顺序!

    		int a1 = 10;
    		int b1 = 11;
    		System.out.println(a1 += b1 += 3);
    等价于

    		int a1 = 10;
    		int b1 = 11;
    		System.out.println(a1 += (b1 += 3));
    打印结果是24

    注意!java里是没有逗号运算符的,在fou循环的表达式()中的逗号,起的是隔离的作用!


    展开全文
  • 优先级最高的是** 幂运算 优先级最低的是= 赋值运算 ( )可以提升运算的优先级 2、整体来讲 一元运算符 > 二元运算符 一元运算符: 同一时间只操作一个值 - ~ 二元运算符: 同一时间操作两个值 + - * / 3、同一层级...

    一、运算

    1、算数运算符(7种)

    + - * / 
    %  (取余数,注意负数的取余,要根据除数的正负,来套用公式判断余数是多少,如果被除数和除数都是负数,就在正常结果前面加上负号)
    ** (幂运算、次方)
    // (地板除,取整除)
    

    2、比较运算符(6种)

    ===是赋值;==是比较)
    !=
    >    <    >=    <=		
    

    3、赋值运算符

    =    +=    -=    *=    /=    %=    **=    //=    :=(py3.8 ,可在表达式内部为变量赋值)
    

    4、位运算符[将数字看做二进制来进行计算]

    """
    a = 15   b = 8
    
    a = 0000 1111
    b = 0000 1000
    
    a&b = 0000 1000		# 8
    a|b = 0000 1111		# 15
    a^b = 0000 0111		# 7
    ~a  = 1001 0000		# -16 
    	var = 15
    	原码:  0000 1111
    	反码:  0000 1111
    	补码:  0000 1111
    	按位非:1111 0000 
    	补码:  1111 0000
    	反码:  1000 1111
    	原码:  1001 0000	# -16
    
    res = 5 << 3	# 5*2的3次幂  40
    res = 28 >> 3 	# 28//2的3次幂 3
    
    """
    
    & 按位与		# 对应位同1则1,否则为0
    | 按位或		# 对应位有1则1
    ^ 按位异或	# 对应位相异为1,相同为0
    ~ 按位取反	# 针对于补码进行操作,按位取反,包括符号位  公式 -(n+1)
    << 左移		# 实现乘法操作
    >> 右移		# 实现除法操作
    
    

    5、逻辑运算符

    and - 布尔与 全真则真,一假则假
    or - 布尔或 全假则假,一真则真
    not - 布尔非 真变假,假变真
    

    逻辑运算的优先级:

    **( )> not > and > or**
    
    res = 5 or 6 and 7		# 5
    res = (5 or 6) and 7	# 7
    res = not(5 or 6) and 7	# False
    res = 1 > 2 or 3 < 4 and 5 > 10 or 11 < 12 and 13 > 16	# False
    	res = False or True and False or True and False
    	res = False or False or False
    	res = False
    

    逻辑短路现象:

    >>> True or print(111)		or 一真则真 直接输出第一个真的,后面不执行了
    True
    
    >>> False and print(111)	and 一假则假 
    False
    
    print(111) or True		# print是内置的函数,函数内部返回的是None,功能是打印,这两者并不冲突
    

    6、成员运算符 [针对于容器型数据]

    in		判断序列中有没有某值
    not in
    
    

    7、身份运算符

    is		判断两个标识符是不是引用自一个对象
    is not	检测两个数据在内存中是不是同一个值
    

    二、总结

    1、个别运算符

    优先级最高的是** 幂运算
    优先级最低的是= 赋值运算
    ( )可以提升运算的优先级

    2、整体来讲 一元运算符 > 二元运算符

    一元运算符: 同一时间只操作一个值 - ~
    二元运算符: 同一时间操作两个值 + - * /

    3、同一层级

    逻辑: ( )> not > and > or
    算数: 乘除 > 加减
    位运算: (<< >> ) > & > ^ > |

    4、其他情况:

    算位比身成逻,赋值运算收尾(赋值运算符用来将算好的值赋值给等号左侧变量,做收尾工作)
    算术运算符(±*/) > 位运算符(&^|<<>>) > 比较运算符(=<>>=<=) > 身份运算符(is isnot) > 成员运算符(in notin) > 逻辑运算符(and or not)

    res = 5 + 5 << 6 // 3 is 40 and False
    
    res =5 + 5) << (6 // 3) is 40 and False
    
    10 << 2 is 40 and True
    
    	40 is 40 and True
    	
    	True and True
    	
    	True
    
    展开全文
  • 如果每个级别的优先级都配置了mod信息, 会按优先级关系逐级覆盖, module信息以最高优先级的配置为准。 如果同级优先级配置里面 modules 和 groups.modules里面有相同的module配置,取决于for in 遍历的顺序。 ...

    背景

    YUI的配置参数较多, 可以在好几个地方配置一个module的相关信息, 如:

    //在全局配置, 所以YUI实例共享
    YUI_config = {
        modules: {
            'w-autcomplete': {
                requires: ['module1'],
                path: 'test1.js',
            }
        },
        groups: {
            modules: {
                'w-autocomplete': {
                    requires: ['module2'],
                    path: 'test2.js'
                }
            }
        }
    }
     
     
    //在某一个YUI实例中配置
    YUI({
    groups: {
        fecore: {
            modules: {
                'w-autocomplete': {
                    base: 'http://fe.com?f=fecore/test3',
                    type: 'js',
                    requires: ['module3'],
                 }
            }
        }
    },
    modules: {
        'w-autocomplete': {
            base: 'http://fe.com?f=fecore/',
            path: 'w-autocomplete/w-autocomplete.js',
            type: 'js',
        }
    }
    }).use('w-autocomplete', function(Y) {
        console.log(Y);
    }); 
     
    //在模块文件中申明配置: w-autocomplete.js
    M.add('w-autocomplete', function(Y) {
        ...
    }, 
    '1.0.0', 
    {
        requires: ['module4']
    }
    );

    这三类情况, YUI在加载的时候到底如何来判断, 使用哪一个module的配置信息呢?

    配置优先级关系

    一个YUI实例config 配置优先级关系从高到底依次为:

    1. YUI(args) 里面的 args, args可以是数组, 数组依次执行 applyConfig(arg[i]);

    2. YUI_config

    3. YUI.GlobalConfig

    YUI初始化的时候会将这些参数按如上所述的优先级进行config初始化。 所以当使用YUI(args).use 的方式来使用模块时, args会自动覆盖当前实例Y的modules 配置信息。 

    当我们需要给所有YUI实例添加配置参数的时候, 可以直接通过配置 YUI_config 或者 YUI.GlobalConfig 来实现。 也可以在YUI或者YUI实例初始化后,进行动态配置,如:

    Y.applyConfig({groups: {}, modules: {} });
    YUI.applyConfig({groups: {}, modules: {} }); 

    处理同一优先级的配置

    对同一优先级的配置使用applyConfig的时候, 由于是Map操作,所以并不能保证config里面的groups & modules的覆盖顺序, 测试了一下for in操作在各个浏览器下的表现, 证明对于key都是字符串的情况,在不同浏览器中执行顺序和书写的顺序有关系。

    所以当groups.modules和modules里面都定义了相同module时, 谁定义在后面就以谁的定义为准 。

    YUI().add定义的模块信息

    YUI().add('w-autocomplete', function(Y) {}, 1.0.0,  {
        requires: ['module1', 'module2'],
        use: ['module3', 'module4']
    }),

    在执行add操作时, YUI.Env.mods里面会新增如下对象:

    {
        detail: {
            requires: ['module1', 'module2']
            use: ['module3', 'module4']
        },
        version: 1.0.0,
        name: 'w-autocomplate',
        fn: fn
    }

    module加载完毕后, 在执行回调之前, 会先检查加载的依赖链中的missing module, missing module包括module定义中的 requires和use。  得到missing列表后,再用Y.use函数进行reload。 

    var missing = [];
    var mods = YUI.Env.mods;
    var process = function(names, skip) {
        var i = 0, a = [], name, len, m, req, use;
        len = names.length;
        for (i = 0; i < len; i++) {
            name = names[i];
            m = mods[name];
            req = null;
            use = null;
            if (m) {
                used[name] = true;
                req = m.details.requires;
                use = m.details.use;
            } else {
                if (!G_ENV._loaded[VERSION][name]) {
                    missing.push(name);
                } else {
                    used[name] = true; // probably css
                }    
            }    
            // make sure requirements are attached
            if (req && req.length) {
                process(req);
            }    
            // make sure we grab the submodule dependencies too
            if (use && use.length) {
                process(use, 1);
            }
        }
    };
    process(modules);
    redo = missing.length;
    if (redo) {
        Y._use(missing, function() {
            if (Y._attach(data)) {
                 Y._notify(callback, response, data)
            }
        });
    }

    so, requires 和 use的定义会影响到loader的加载。而和在前面两种方式配置模块的requires和use 主要区别是, 前两种配置依赖的module会在当前模块加载之前加载, 而后面这种方式会在当前模块加载完成后再进行加载。

    但是如果在module里面定义的其他信息,如condition等,loader会忽略不管。

    总结

    如果每个级别的优先级都配置了mod信息, 会按优先级关系逐级覆盖, module信息以最高优先级的配置为准。

    如果同级优先级配置里面 modules 和 groups.modules里面有相同的module配置,取决于for in 遍历的顺序。 测试各浏览器结果会按照定义顺序, 后定义的module信息优先级更高。  

     

    转载于:https://www.cnblogs.com/mininice/p/3914786.html

    展开全文
  • 在业务中存在着这样的一种场景,任务自身有着优先级区分。高优先级的任务要先于低优先级的任务执行。但是如果一直持续不断的有高优先级任务添加到队列,可能会导致低优先级任务无法分配执行资源而被饿死。 因此除了...

    在业务中存在着这样的一种场景,任务自身有着优先级区分。高优先级的任务要先于低优先级的任务执行。但是如果一直持续不断的有高优先级任务添加到队列,可能会导致低优先级任务无法分配执行资源而被饿死。

    因此除了优先级控制外,已经入队到优先级线程池中的低优先级任务需要有一种机制可以实现优先级的晋升。这样才能避免在线程池中的低优先级任务被饿死。

    本场 Chat 会从整个线程池的设计思路,并发过程,数据结构的推导开始分析,从 0 到 1 论证整个线程池的构成。

    在本场 Chat 中,会讲到如下内容:

    • 什么样的数据结构可以高性能的完成优先级晋升这个任务。
    • 内部任务的优先级晋升如保证不会被外部持续加入的高优先级任务饿死。
    • 任务提取,任务放入,任务优先级变更,如何保证并发安全

    引言

    在技术群讨论到一个有意思的业务需求,可以描述为:

    有一个内部按照优先级进行任务排序的线程池。线程池会优先执行高优先级的任务。随着时间的流逝,线程池内部低优先级的任务的优先级会逐渐晋升变为高优先级,以避免被不断新增的高优先级任务阻塞导致饿死。

    考虑到 JDK 已经为开发者提供了自定义线程池ThreadPoolExecutor以及优先级队列PriorityBlockingQueue,两者相结合并且定期调整队列中低优先级任务的优先级再进行resort将低优先级的任务调整到队列的前头,也可以一定程度上避免被饿死。

    这种方案的问题在于resort的消耗比较高,并且还需要重新计算每一个任务的优先级。为此,引出我们下面的设计,希望使用无锁并发的数据结构存储任务,并且任务支持自动的优先级晋升,保证低优先级的任务最终能够执行而不会被不断增加的高优先级任务饿死。

    欢迎加入技术交流群 186233599 讨论交流,也欢迎关注笔者公众号:风火说。

    推导过程

    如何实现优先级晋升

    声明一个数组,按照循环队列的方式使用。每一个数组槽位上都挂载一个任务列表。有一个当前指针指向数组中的某一个槽位,该槽位即为当前最高优先级任务插入的槽位。指针数字递增方向优先级依次降低。指针以某种方式沿递增方向移动,因为指针指向的槽位代表最高优先级,因此指针的移动实际上意味着所有槽位的优先级都晋升了。

    那么这里的优先级只能是离散化的整型数字,并且优先级的范围为 0 到 数组长度减 1 。最高优先级为 0。

    用图形化的方式表达就是如下的情况

    图中优先级的范围是[0,6],current 指针指向的槽位即为最高优先级,current 左侧槽位为最低优先级,current 右侧槽位为次高优先级。每一个槽位上都挂载一个队列,队列中的任务的优先级都相同(后续算法中可以看到会有不同的优先级混合)。

    每次取任务时总是从 current 指针指向的槽位的队列读取任务。当一定时间流逝后,current 指针沿着右侧移动一位,此时意味着所有槽位的优先级都被晋升了,除了原本的 current 指向的槽位,它变为了最低优先级槽位。

    由于 current 指针总是在移动,因此最终会移动到之前低优先级的槽位,此时该槽位下的任务就成了最高优先级任务,被读取执行。这样就避免了在运行过程不断有高优先级任务被加入导致原本的低优先级饿死的情况发生。

    数据结构设计

    根据上面的优先级晋升思路,显然应该有一个数组,其不同的槽位代表着不同的优先级。每一个槽位上挂载一个 MPMC 类型的队列,用于该优先级下任务的添加和读取。

    使用一个当前指针,该指针指向的槽位为最高优先级槽位。

    一个指针产生的问题

    如果只有一个指针,意味着读取任务时,从该指针指向的槽位读取,因此此时指针指向的槽位是最高优先级。而插入任务的时候,需要根据当前指针进行计算。这种模式在优先级晋升时存在并发问题。

    当指针从槽位 1 指向更新到槽位 2。此时槽位 1 可能还存在部分剩余的任务,这部分任务的实际优先级应该是高于槽位 2 当中的。而如果在这个时候插入最低优先级的任务,可能就会插入到槽位 1 中。那么槽位 1 的任务队列实际就混合了最高优先级和最低优先级的任务,无法区分。

    为了解决不同优先级任务在同一个队列中混合的问题,我们可以在指针移动时,将之前槽位的剩余移动到当前槽位的队列头。这实际上就意味着要求队列是出于双端队列模式。但是因为指针移动和任务移动无法原子化进行,还是会造成槽位 1 的队列中最高优先级任务和最低优先级任务混在一起的情况。

    从实现效果而言,我们需要的是在指针移动的时候,保证槽位 1 中剩余的原本高优先级的任务执行完毕后才能去执行槽位 2 这个“原本的次高优先级,现在的最高优先级“的任务。从效果来看,并不需要一定移动任务,可以通过一种手段,保证槽位 1 中原本高优先级任务执行完毕后再去执行槽位 2 的任务即可。

    基于这种考量,我们将一个指针拆分为两个:任务插入指针和任务读取指针。

    任务插入指针和任务读取指针

    基于并发读写的考虑,两个指针都是AtomicInteger类型。两个指针的作用分别为:

    • 任务插入指针:该指针指向的槽位为当前最高优先级槽位(后续会引入轮次这个概念,因此这里对当前加粗)。
    • 任务读取指针:从结构体中获取任务时使用该指针指向的槽位上获取任务队列进行任务读取。

    任务插入指针和任务读取指针分离的好处在于,任务插入指针的移动意味着不同槽位优先级的实际晋升。而读取可以依照读取指针指向的槽位上的队列读取任务,直到对应优先级的任务读取完毕后再移动读取指针到下一个槽位。这样一来,保证了按照入列的顺序被公平的处理,也保证了同一个时间单位高优先级的任务优于低优先级任务被处理,也避免了单一指针移动需要的任务拷贝带来的不同优先级任务污染问题。

    任务插入指针如何移动

    插入指针可以按照两种策略移动:

    • 自然时间流逝移动,一定时间后移动。
    • 以读取次数为单位,一定次数后移动。

    如果选择策略一,需要后台配置一个线程,按照固定时间移动插入指针;如果选择策略二,需要一个全局的AtomicInteger对象,用于次数判定。

    如果选择方案一,可能会存在一种场景,往线程池中投入了大量的同一个优先级的任务,使得某个槽位上的队列长度很长。如果任务处理相对缓存,则任务插入指针可能会被移动多次。这种移动会使得槽位上队列有了很多不同优先级的任务。而读取任务时按照优先级逐步去处理,这使得产生了这么多不同的优先级实际上意义是不大的。

    因此采用策略二会更加合适一些。

    由于读取任务时是多线程的,因此策略二实现上需要注意的点包括:

    • AtomicInteger#incrementAndGet实现任务读取次数累加。如果返回的数字是阈值的倍数,则意味着可以移动任务插入指针。
    • 使用AtomicInteger#incrementAndGet来移动插入指针。

    在这里对插入指针移动的并发考量在于,由于读取线程对读取计数使用AtomicInteger#incrementAndGet方式累加是必然成功,而返回数值是晋升阈值的倍数时必然需要实现插入指针的递增。因为递增的必然性,因此同样使用AtomicInteger#incrementAndGet方式来实现。

    任务插入指针移动到同一位置导致的优先级任务混合问题

    假定系统初始状态,插入和读取指针都指向了槽位 1,在槽位 1 上插入了大量的任务。随着任务的读取,插入指针移动到了槽位 2,此时该槽位上插入了一些任务。随着任务的读取,插入指针继续移动,移动过数组的长度后,再次指向了槽位 2。假定此时读取指针仍然在槽位 1,而如果这个时候插入插入任务。那么实际上槽位 2 队列中任务应该分为两种:前半部分是上一个轮次插入的任务,后半部分是当前刚插入的任务

    如果读取指针移动到槽位 2,应该将前半部分任务执行完毕后就去执行槽位 3 上的任务,而不是将所有的任务都执行完。因此槽位 3 上的任务实际优先级应该高于槽位 2 队列中后半部分的任务。

    基于上述情况,问题可以转化为依靠读取指针在读取任务时,如何识别当前队列中不是本轮次要处理的任务进而移动读取指针?

    考虑到任务插入指针和任务读取指针本身是有值的,这个值单调递增,实际上可以看成是一种“顺序”概念的表达。因此任务的准备添加时,可以将插入指针的值加上任务的优先级,声明为任务的插入优先级。读取指针在读取任务时,只有当前任务的插入优先级等于读取指针的值,意味着该任务时本轮次读取指针应该要处理的任务。如果读取的任务的插入优先级与读取指针不等时,意味着当前队列不能再读取任务,应该移动读取指针。

    通过任务本身的插入优先级避免了不同轮次的任务在一个队列中被混合导致的优先级混乱。

    任务读取指针如何移动

    上个章节提出任务的插入优先级,解决了不同轮次的任务在同一个队列可能会混合的问题。这个问题的解决引出了读取指针的移动策略:在读取到的任务的插入优先级与读取指针的值不等时意味着需要移动。

    但是这里又产生了新的问题:并发移动读取指针的问题。在读取并发的情况下,会遇到一个问题:读取出来的任务的优先级不符合指针,此时要重新放回队列,但是重新放入,就可能和任务的插入混合,造成数据混乱。

    有几种可能的解决方式:

    • 任务的读取采用Sync关键字修饰,如果读取任务不符合,则放回,并且移动指针。由于没有读取并发,但仍然可能因为读取的放回和新任务的添加造成数据混乱。
    • 采用分段机制,每一个分段是一个队列,分段和分段构成一个队列。一个分段内的优先级是固定的,因此当分段耗尽时,就是切换读取指针的时候。

    策略一并不能彻底解决问题,在这里我们采用策略二的方案。

    策略二的引入实际上改变了上面的一个数据结构,也即是数组存储的元素不再是一个任务队列,而是一个分段队列。而每一个分段内部又存储了任务队列,并且分段的队列的任务的插入优先级均是相同的。这意味着分段在创建的时候就具备了插入优先级这个值。分段和分段的插入优先级必然不同,这个结构就天然的支持了轮次的概念。

    分段结构的引入导致了数据结构的变化,这实际上会改变任务插入和任务读取的流程。下文会再来细说具体的实现。分析到这里,读取指针的移动时机就很明白了,在分段内数据耗尽,就意味着某个具体插入优先级的任务都被读取完毕了。

    当然,考虑到读写并发的原因,读取线程发现分段内数据耗尽并不意味着该插入优先级的任务全被读取了,后文会针对并发场景在处理流程上解决。

    插入和读取并发

    插入和读取可能在同一个槽位同一个分段上并发。分段的队列本身是支持 MPMC 的,这并没有问题。

    可能会出现一种并发异常就是插入线程读取了插入指针的值,并且准备插入数据,但是因为线程调度的原因,失去了 CPU 资源,尚未完成数据插入。此时读取线程将槽位内的任务读取完毕后认为没有数据,则移动了读取指针到下一个槽位。在读取指针移动后,插入线程才完成数据的插入。这样导致本来应该是高优先级的任务变成最低优先级槽位上的任务。而当下一轮次读取指针再次指向该槽位时,读取指针获取的到任务的任务优先级又会和读取指针本身的数值冲突。

    针对并发的异常场景,有一种常见的解决思路就是二次检查。也就是读取线程在移动任务读取指针后,再次检查下当前分段内是否出现了新的任务,如果有,则协助迁移到下一个槽位上;写入线程在放入任务后,检查是否读取指针移动过,如果有,则协助迁移到下一个槽位上。

    然而,读取线程检查分段内的队列是否剩余,写入线程检查读取指针是否移动,这些状态都是在动态变化的,仍然会产生一些其他问题。双重检查一般会引入一个终止状态来来减少可能的变化场景。在这里,我们为分段引入状态:使用中和终止。一个分段初始化时是使用中状态,当读取线程认为该分段内的任务都被消耗后,则应该更新为终止状态。一旦分段进入终止状态,则被抛弃,不应该再有任务数据添加到该分段中。

    通过分段状态,我们可以将任务区分为终止前添加到分段和终止后添加到分段两类。前者需要被正常读取,后者则需要迁移到其它合适的分段中再被处理。

    到这里为止,我们针对数据结构和其元素属性的变化就完成了。

    将数组通过循环队列的方式来表达不同的优先级。通过任务写指针的移动来实现内部任务优先级的晋升。通过读指针来实现任务严格按照优先级顺序被处理,且避免低优先级任务被高优先级任务饿死。数组的元素指向一个该槽位上插入优先级最低的分段。一同散列到同一个槽位上的分段按照插入优先级的顺序形成队列。

    代码实现

    整个代码当中,最为复杂的就是任务的插入和读取,下面分别来设计流程。

    任务插入

    上面推导过程分析了插入和读取并发可能导致的冲突场景。这里我们细化其解决流程。对于插入线程而言,要处理的情况包括有:

    • 元素对应槽位上没有分段。
    • 元素对应槽位上的分段的插入优先级和插入指针的值不相等。
    • 元素对应槽位上分段列表中插入优先级与插入指针相符的分段处于终止状态
    • 元素对应槽位上的分段插入优先级与插入指针相等,且处于使用状态。

    可以看到,只有第四种情况任务可以在当前分段插入成功,且插入完毕后还需要再次检查分段的状态。基于这些考量,我们将插入流程设计为

    可以看到,这个流程中没有处理槽位上没有分段的情况,这个在下一个章节我们会分析。

    任务的读取

    有了分段的存在,读取指针的移动判定更加复杂,读取线程可能碰到的场景有:

    • 读取指针散列的槽位上没有分段。
    • 读取指针散列的槽位上有分段且状态为使用,分段内没有任务。
    • 读取指针散列的槽位上有分段且状态为使用,分段内有任务。
    • 读取指针散列的槽位上有分段且状态为关闭,分段内没有任务。
    • 读取指针散列的槽位上有分段且状态为关闭,分段内有任务。

    只有第三种情况可以读取任务并且进行处理。有了轮次这个概念,读取指针永远只会读取槽位上的第一个分段。如果槽位上没有分段,或者分段的插入优先级与读取指针不同,或者分段内没有任务,则可以考虑移动读取指针。注意,分段状态为关闭并不是读取指针移动的条件,原因下面会分析。

    但是移动读取指针的时候首先需要考虑当前读取指针是否已经处于(写入指针的值+最低优先级数字),如果是的话,意味着已经处于边界,不应该在移动。

    分段状态的更新只能由读取线程来进行。当读取线程发现该分段已经没有任务了,首先应该通过 CAS 的方式更新分段状态。CAS 竞争成功的线程再次检查分段内是否出现了新的任务,如果出现的话,则提取任务,完成任务读取。为何不将任务移动到下一个槽位。因为下一个槽位上可能还没有分段,此时读取线程可能和写入线程竞争槽位上的分段写入。如果写入线程竞争成功,读取线程移动过去的任务数据的优先级就放到了错误的分段中;如果读取线程竞争成功,则读取线程创建的分段必须是第一个分段,否则任务还是移动到错误的地方。

    解决这个问题最好的办法就是不解决。不移动任务,仍然在该分段上读取任务直到任务耗尽。然后再尝试移动读取指针。而对于写入线程而言,当其发现分段的状态变为终止后,是提取出任务重新执行完整的放入流程,不会有并发的问题。

    再次梳理下没有任务情况下的流程,应该是通过 CAS 修改分段的状态。无论成功或失败,都可以继续检查队列是否有任务,如果有的话,则返回读取到的任务。如果没有的话,则 CAS 将读取指针+1。竞争成功的线程将当前分段的下一个分段设置给槽位,并且重新执行读取流程。竞争失败的线程则反复检查读取指针的值,发现变化后,重新执行读取流程。

    这里有一个并发冲突需要考虑,当读取线程尝试将当前分段的下一个分段设置为槽位的值时,可能此时当前分段的下一个分段是 null,而写入线程正在尝试为当前分段设置下一个分段。这种情况下可能导致下一个分段丢失。特别的,如果当前分段的下一个分段已经被设置,并且有任务被放入其中,丢失这个分段就意味着数据丢失。

    为了避免这个情况,在当前分段的下一个分段为 null 时,就不能将下一个分段(属性值)设置给槽位。这使得在读取到分段时,需要首先检查分段的优先级,确认是否本轮次。如果是的话,再执行后续的流程。否则要么移动(该分段没有下一个分段),要么将该分段的下一个分段设置给槽位后,在移动。

    从这个角度出发,我们可以在初始化的时候,将数组中的元素都填充一个分段。这样写入线程就不需要处理槽位上可能为空的场景了。

    基于此,我们将读取任务的变化为:

    • 槽位上的分段优先级小于读取指针,且分段状态为终止。
    • 槽位上的分段优先级等于读取指针。
    • 槽位上的分段优先级大于读取指针。

    第一种情况,如果该分段有下一个分段,CAS 更新到槽位上;如果没有,则 CAS 移动读取指针。

    第二种情况,按照上面分析的流程进行处理即可。

    第三种情况,CAS 移动读取指针。

    综上,我们可以将读取流程设计为

    包装为 BlockQueue

    在 JDK 提供的ThreadPoolExecutor类的构造方法中,需要传入BlockingQueue作为队列的接口。显然,上述的存储结构并不能支持BlockQueue,需要考虑包装。

    显然,上面的存储结果在写入的时候并不会阻塞,因此只需要考虑如何包装读取数据不存在时的阻塞等待即可。

    简单的方式就是在读取失败的获取锁,并且在队列空的condition对象执行等待;插入任务的时候执行唤醒。

    效果展现

    测试代码如下

    首先添加一定量的高优先级任务,随后添加 5 个低优先级,最后通过CountLatch模拟在运行过程中添加高优先级任务。

    如果单纯按照优先级排序,则需要所有高优先级任务输出完毕后才会输出低优先级任务,显然这是错误的。正确的实现应该是先输出第一批高优先级任务,再输出低优先级任务,最后输出第三批高优先级任务。运行代码,看到结果如下

    与我们的预期相吻合。

    代码托管地址

    Gitee:https://gitee.com/ericds/ericarticle/blob/master/优先级自动晋升线程池/AutoPromotePriorityQueue.java

    阅读全文: http://gitbook.cn/gitchat/activity/5e186e65c5043527dc4ee2ae

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • 需求优先级划分技巧

    千次阅读 2018-12-11 14:01:16
    01 优先级 开场语 假设需求梳理会议上,团队确定本迭代的待办事项有n个,假若等到两周迭代开发快结束的时候,还有2个未完成,在这种情况下,是否按照约定准时发布? 按敏捷的思想,答案非常明确:按时进入测试和...
  • 工作任务优先级

    2019-09-10 17:06:04
    针对工作任务,有先有后,此处整理了一些工作任务优先级原则: 1、本部门领导发布的...3、需要提前梳理,后续需要花时间执行的优先,这样可以预留时间来处理,避免延期 4、如果不清楚优先级,让领导安排优先级 ...
  • where优先级

    2018-08-27 15:58:00
    select name from emply where id >5; 先找表from emply 再找条件 where id >...先找到表,然后where一条条数据过滤,发现符合的就扔给select,select再梳理出自己需要的数据进行打印。 #...
  • 细说C语言优先级

    2013-11-28 17:09:59
    0. 为什么要掌握优先级 想想这两个问题: a. 读别人的代码,遇到优先级问题看不懂,怎么办? b.... 本想贴一张画来装饰墙壁,却用了一...有些东西一定要梳理,总结。 1. 优先级 1.1 优先级图表 优先级最高者不
  • C#中运算符的优先级

    2017-12-29 16:31:32
    在运算符的使用过程中,免不了要去梳理运算符的优先级,防止计算出错。下面给出运算符的优先级排序表格: 常用运算符的介绍和使用:点击打开链接 对于需要二元运算符(需要两个操作数)来说,几乎所有的运算符都是左...
  • c语言的优先级

    2012-09-27 15:23:45
    0. 为什么要掌握优先级 1. 优先级 1.1 优先级图表 1.2 运算符实例 1.3 优先级顺口溜 2. 结合性 3. 参考资料 Link:http://blog.chinaunix.net/space. ... blog&id=2880933  写代码的时候,常会翻看的一个表...
  • c 语言优先级

    2013-12-10 23:02:27
     写代码的时候,常会翻看的一个表就是“c语言运算符优先级表”。c的运算符优先级常常很让人头疼。其实,在大学里学习c的时候,老师告诉大家这个不用一定背下来,用的时候可以找书,或者加小括号就可以了。我听了,...
  • 细说C语言的优先级

    2013-07-02 17:36:28
    0. 为什么要掌握优先级  想想这两个问题:  a. 读别人的代码,遇到优先级问题看不懂,怎么办?  b. 一堆的括号,美观吗?  本想贴一张画来装饰墙壁,却用了一堆纸来固定!   有人说代码写多了,自然就会...
  • C运算符优先级

    千次阅读 2013-04-05 20:01:39
    优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右   () 圆括号 ...
  • 关于CSS优先级问题

    2018-10-12 17:53:01
    import,今天在老大的指引下才发现这里面也大有文章,下面梳理一下我自己的理解。  首先,css优先级顺序:内联样式 &gt; id选择器 &gt; 类选择器 = 属性选择器 = 伪类 &gt; 元素选择器 &gt; 通用...
  • 细说c语言的优先级

    千次阅读 2011-09-26 13:42:22
    0. 为什么要掌握优先级 1. 优先级 1.1 优先级图表 1.2 运算符实例 1.3 优先级顺口溜 2. 结合性 3. 参考资料  写代码的时候,常会翻看的一个表就是“c语言运算符优先级表”。c的运算符优先级常常很让人头疼...
  • 线程的常用方法梳理

    2021-07-22 13:53:02
    线程的优先级_setPriority8. 设置守护线程_setDaemon 1.线程的五大状态 创建状态 就绪状态 阻塞状态 运行状态 死亡状态 2.线程停止 不推荐使用jdk提供的stop()、destory()方法。【已废弃】 推荐线程自己停止下来...
  • 选择器优先级&CSS&Margin和Padding&JavaScript HTML 列表标签 <ul> <li>菜单项01</li> <li>菜单项02</li> <li>菜单项03</li> <li>菜单项04</li...
  • C语言的运算符优先级

    2012-03-27 15:26:42
    原文地址: ... Table ...0. 为什么要掌握优先级 1. 优先级 1.1 优先级图表 1.2 运算符实例 1.3 优先级顺口溜 2. 结合性 3. 参考资料 Link:http://blog.chinaunix.net/space. ... blog&id=2880933

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,415
精华内容 6,566
关键字:

优先级梳理方法