• 关于

    单位元素可以做什么

    的搜索结果

回答

1.首先说一下你写的这段代码,这段代码是有效的,代表整个页面默认字体大小为16*2,16px字体大小是初始化时网页默认的大小,你的是200%,当然也是按照2倍计算的。2.你说的html的父元素,这个可能有点问题,你想知道的应该是根元素document.documentElement的字体大小是多少对吧,我不说是多说,你自己运行一下就会一目了然window.getComputedStyle(document.documentElement,null)['fontSize']3.字体的规定可以用百分数,px,em,rem或者其他的单位,看你想做什么样的页面了,固定尺寸的就用px就够了,但是如果要做响应式rem常用一些,兼容低版本自然会有相应的办法,如sass中的计算方式,或者通过js来改变根元素的大小。em相对于父元素来决定,而rem是根据html的字体大小来决定的。

杨冬芳 2019-12-02 02:32:30 0 浏览量 回答数 0

回答

1.首先说一下你写的这段代码,这段代码是有效的,代表整个页面默认字体大小为16*2,16px字体大小是初始化时网页默认的大小,你的是200%,当然也是按照2倍计算的。2.你说的html的父元素,这个可能有点问题,你想知道的应该是根元素document.documentElement的字体大小是多少对吧,我不说是多说,你自己运行一下就会一目了然window.getComputedStyle(document.documentElement,null)['fontSize']3.字体的规定可以用百分数,px,em,rem或者其他的单位,看你想做什么样的页面了,固定尺寸的就用px就够了,但是如果要做响应式rem常用一些,兼容低版本自然会有相应的办法,如sass中的计算方式,或者通过js来改变根元素的大小。em相对于父元素来决定,而rem是根据html的字体大小来决定的。最后,你可以多动手写一下,这样不但加深印象,也会认识的更深刻,多去一些css,前端网站看看,如大漠大大的w3cplus和sass中国,这是一篇文章,你可以看看http://www.cn-sass.com/%E6%8A%80%E5%B7%A7/sass-px-to-rem-with-mixin-and-function.html

a123456678 2019-12-02 02:23:49 0 浏览量 回答数 0

回答

基础类 常见十大算法 优劣术语稳定性 原本a在b前,a=b,排序之后位置任然不变。不稳定性则相反内排序 所有排序都在内存中完成。外排序数据放磁盘,排序通过磁盘内存的数据传输事件复杂度 算法执行耗费的时间 空间复杂度 算法执行耗费的内存 In/out-place: 不占/占额外内存 冒泡排序: 选择排序: 插入排序: 希尔排序: 归并排序: 快速排序: 堆排序: 计数排序: 桶排序: 基数排序: 提高类 常见算法面试题 Problem 1 : Is it a loop ? (判断链表是否有环?) Assume that wehave a head pointer to alink-list. Also assumethat we know the list is single-linked. Can you come upan algorithm to checkwhether this link list includes a loop by using O(n) timeand O(1) space wheren is the length of the list? Furthermore, can you do sowith O(n) time and onlyone register? 方法:使用两个指针,从头开始,一个一次前进一个节点,一个前进2个节点,则最多2N,后两个指针可以重合;如果无环,则正常停止。 同样的,可以找到链表的中间节点。同上。 Problem 2:设计一个复杂度为n的算法找到链表倒数第m个元素。最后一个元素假定是倒数第0个。 提示:双指针查找 Problem 3:用最简单的方法判断一个LONG整形的数A是2^n(2的n次方) 提示:x&(x-1) Problem 4:两个烧杯,一个放糖一个放盐,用勺子舀一勺糖到盐,搅拌均匀,然后舀一勺混合物会放糖的烧杯,问你两个烧杯哪个杂质多? 提示:相同。假设杂质不等,那么将杂质放回原杯中,则杯中物体重量必变化,不合理。 Problem 5:给你a、b两个文件,各存放50亿条url,每条url各占用64字节,内存限制是4G,让你找出a、b文件共同的url。 法1:使用hash表。使用a中元素创建hash表,hash控制在适当规模。在hash中查找b的元素,找不到的url先存在新文件中,下次查找。如果找到,则将相应的hash表项删除,当hash表项少于某个阈值时,将a中新元素重新hash。再次循环。 法2:对于hash表项增加一项记录属于的文件a,b。只要不存在的表项即放入hash表中,一致的项则删除。注意:可能存在很多重复项,引起插入,删除频繁。 Problem 6:给你一个单词a,如果通过交换单词中字母的顺序可以得到另外的单词b,那么定义b是a的兄弟单词。现在给你一个字典,用户输入一个单词,让你根据字典找出这个单词有多少个兄弟单词。 提示:将每个的单词按照字母排序,则兄弟单词拥有一致的字母排序(作为单词签名)。使用单词签名来查找兄弟单词。 Problem 7:五桶球,一桶不正常,不知道球的重量和轻重关系,用天平称一次找出那桶不正常的球。 Problem 8:给两个烧杯,容积分别是m和n升(m!=n),还有用不完的水,用这两个烧杯能量出什么容积的水? m, n, m+n, m-n以及线性叠加的组合 Problem 9:写出一个算法,对给定的n个数的序列,返回序列中的最大和最小的数。 Problem 10:你能设计出一个算法,只需要执行1.5n次比较就能找到序列中最大和最小的数吗?能否再少? 提示:先通过两两比较,区分大小放入“大”,“小”两个数组中。从而最大数在“大”数组中,最小数在“小”数组中。 Problem 11:给你一个由n-1个整数组成的未排序的序列,其元素都是1到n中的不同的整数。请写出一个寻找序列中缺失整数的线性-时间算法。 提示:累加求和 Problem 12:void strton(constchar* src, const char*token) 假设src是一长串字符,token存有若干分隔符,只要src的字符是token中的任何一个,就进行分割,最终将src按照token分割成若干单词。找出一种O(n)算法? 提示:查表的方法,将所有的字符串存储在长度为128的数组中,并将作为分隔符的字符位置1,这样即可用常数时间判断字符是否为分隔符,通过n次扫描,将src分割成单词。 Problem 13:一个排好序的数组A,长度为n,现在将数组A从位置m(m<n,m未知)分开,并将两部分互换位置,假设新数组记为B,找到时间复杂度为O(lgn)的算法查找给定的数x是否存在数组B中? 提示:同样采用二分查找。核心思想就是确定所查找数所在的范围。通过比较3个数(头,尾,中间)和所查找数之间的关系,可以确定下次查找的范围。 Problem 14:一个排好序的数组A,长度为n,现在将数组A从位置m(m<n,m已知)分开,并将两部分互换位置,设计一个O(n)的算法实现这样的倒置,只允许使用一个额外空间。(循环移位的效率不高) 提示:(A’B’)’ =BA Problem 15:给出Vector的一个更好实现。(STL的vector内存的倍增的,但是每次倍增需要拷贝已存元素,平均每个元素需要拷贝一次,效率不高) 提示:可使用2^n的固定长度作为每次分配的最小单位,并有序的记录每个块的首地址。这中结构同样可以实现线性查找,并且拷贝代价很低(仅有指针) Problem 16:给出已排序数组A,B,长度分别为n,m,请找出一个时间复杂度为(lgn)的算法,找到排在第k位置的数。 提示:二分查找。 Problem 17:给出任意数组A,B,长度分别为n,m,请找出一个时间复杂度为(lgn)的算法,找到排在第k位置的数。 提示:通过最小堆记录k个数,不断更新,扫描一次完毕。 这个提示有问题,求最优算法! Problem 18:假设数组A有n个元素,元素取值范围是1~n,判定数组是否存在重复元素?要求复杂度为O(n)。 法1:使用n的数组,记录元素,存在记为1,两次出现1,即重复。 法2:使用m的数组,分别记录大小:n/m, 2n/m …..的元素个数。桶方法 法3:累加求和。可用于求仅有一个元素重复的方法。 Problem 19:给定排好序的数组A,大小为n,现给定数X,判断A中是否存在两数之和等于X。给出一个O(n)的算法。 提示:从中间向两边查找。利用有序的条件 Problem 20:给定排好序的数组A,大小为n,请给出一个O(n)的算法,删除重复元素,且不能使用额外空间。 提示,既然有重复,必有冗余空间。将元素放入数组的前面,并记录下次可放位置,不断向后扫描即可。 Problem 21:给定两个排好序的数组A,B,大小分别为n,m。给出一个高效算法查找A中的哪些元素存在B数组中。 注意:一般在大数组中执行二分查找,将小数组的元素作为需查找的对象。 更优算法(轩辕刃提供):可以使用两个指针遍历AB,比较当前大小就可以了...时间复杂度o(n+m) Problem 22:问:有1000桶酒,其中1桶有毒。而一旦吃了,毒性会在1周后发作。现在我们用小老鼠做实验,要在1周内找出那桶毒酒,问最少需要多少老鼠。 答案:10只。将酒编号为1~1000 将老鼠分别编号为1 2 4 8 16 32 64 128 256 512 喂酒时 让酒的编号等于老鼠编号的加和如:17号酒喂给1号和16号老鼠 76号酒喂给4号、8号和64号老鼠 七天后将死掉的老鼠编号加起来 得到的编号就是有毒的那桶酒 因为2的10次方等于1024 所以10只老鼠最多可以测1024桶酒 证明如下:使用二进制表示:01, 10, 100, 1000,… , 1,000,000,000。对于任何一个小于1024的数,均可以采用前面的唯一一组二进制数来表示。故成立。 Problem 23:设计一组最少个数砝码,使得天平能够称量1~1000的重量。 如果砝码只能放单边,1,2 ,4 , 512最好。(只能单加) 如果允许砝码双边放,1, 3, 9, 27…. 最好。(可加可减)已知1,3,如何计算下一个数。现可称重量1,2,3,4。设下个数为x,可称重量为, x-4, x-3, x-2, x-1, x, x+1,x+2, x+3, x+4。为使砝码最好,所称重量应该不重复(浪费)。故x=9。同理,可得后面。 图形算法题 Problem 24:如何判断一个点是否在一个多边形内? 提示:对多边形进行分割,成为一个个三角形,判断点是否在三角形内。 一个非常有用的解析几何结论:如果P2(x1,y1),P2(x2,y2),P3(x3,y3)是平面上的3个点,那么三角形P1P2P3的面积等于下面绝对值的二分之一: | x1 y1 1 | | x2 y2 1 | = x1y2 + x3y1 + x2y3 –x3y2 – x2y1 – x1y3 | x3 y3 1 | 当且仅当点P3位于直线P1P2(有向直线P1->P2)的右侧时,该表达式的符号为正。这个公式可以在固定的时间内,检查一个点位于两点确定直线的哪侧,以及点到直线的距离(面积=底*高/2)。 这个结论:可以用来判断点是否在点是否在三角形内。法1:判断点和三角形三边所行程的3个三角形的面积之和是否等于原来三角形的面积。(用了三次上面的公式)。 法2:判断是否都在三条边的同一边,相同则满足,否则不在三角形内。 Problem 25:给出两个n为向量与0点形成角的角平分线。 提示:对两条边进行归一化,得到长度为1的两点,取两个的中点即可。 实战类型 1,确定函数名字与原型 2,严进宽出 3,边界考虑 4,出错处理 5,性能优化(时间复杂度,空间复杂度) 6,循环的掌握 7,递归的应用 8,2个指针跑步 9, Hash算法

happycc 2019-12-02 02:11:37 0 浏览量 回答数 0

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

问题

【精品问答】python技术1000问(1)

问问小秘 2019-12-01 21:57:48 454222 浏览量 回答数 19

问题

Java基础测试题2 | 基础不能都忘掉

游客pklijor6gytpx 2019-12-01 22:04:25 575 浏览量 回答数 0

问题

【今日算法】4月30日-回溯算法详解

游客ih62co2qqq5ww 2020-04-30 14:13:51 9 浏览量 回答数 1

回答

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类。另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收。如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,那么内存回收也就可以根据每个区域的特定进行回收,比如像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就需要使用经典的回收算法来进行回收了,所以看起来分类这么麻烦,其实是大有好处的。 提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。但是·虚拟机的内存结构远比此要复杂的多。除了我们所认识的(还没有认识完全)的堆栈以外,还有程序计数器,本地方法栈和方法区。我们平时所说的栈内存,一般是指的栈内存中的局部变量表。 从图中可以看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,非常好理解,我们知道每一个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被所有线程一起使用的,不管有多少个线程,这片空间始终就这一个。而线程的独占区,是每个线程都有这么一份内存空间,每个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不代表实际内存区域的大小,实际运行过程中,内存区域的大小也是可以动态调整的。下面来具体说说每一个区域的主要功能。 程序计数器,我们在写代码的过程中,开发工具一般都会给我们标注行号方便查看和阅读代码。那么在程序在运行过程中也有一个类似的行号方便虚拟机的执行,就是程序计数器,在c语言中,我们知道会有一个goto语句,其实就是跳转到了指定的行,这个行号就是程序计数器。存储的就是程序下一条所执行的指令。这部分区域是线程所独享的区域,我们知道线程是一个顺序执行流,每个线程都有自己的执行顺序,如果所有线程共用一个程序计数器,那么程序执行肯定就会出乱子。为了保证每个线程的执行顺序,所以程序计数器是被单个线程所独显的。程序计数器这块内存区域是唯一一个在jvm规范中没有规定内存溢出的。 java虚拟机栈,java虚拟机栈是程序运行的动态区域,每个方法的执行都伴随着栈帧的入栈和出栈。 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中包括了局部变量表,操作数栈,方法返回地址以及额外的一些附加信息,在编译过程中,局部变量表的大小已经确定,操作数栈深度也已经确定,因此栈帧在运行的过程中需要分配多大的内存是固定的,不受运行时影响。对于没有逃逸的对象也会在栈上分配内存,对象的大小其实在运行时也是确定的,因此即使出现了栈上内存分配,也不会导致栈帧改变大小。 一个线程中,可能调用链会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是最有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的字节码指令仅对当前栈帧进行操作。Ft5rk58GfiJxcdcCzGeAt8fjkFPkMRdf 局部变量表:我们平时所说的栈内存一般就是指栈内存中的局部变量表。这里主要是存储变量所用。对于基本数据类型直接存储其值,对于引用数据类型则存储其地址。局部变量表的最小存储单位是Slot,每个Slot都能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。 既然前面提到了数据类型,在此顺便说一下,一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress八种类型。前面六种不需要多解释,大家都认识,而后面的reference是对象的引用。虚拟机规范既没有说明它的长度,也没有明确指出这个引用应有怎样的结构,但是一般来说,虚拟机实现至少都应当能从此引用中直接或间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。而returnAddress是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。 对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的Slot空间。Java语言中明确规定的64位的数据类型只有long和double两种(reference类型则可能是32位也可能是64位)。值得一提的是,这里把long和double数据类型读写分割为两次32读写的做法类似。不过,由于局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的Slot是否是原子操作,都不会引起数据安全问题。 操作数栈是一个后入先出(Last In First Out, LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到字节码文件中,关于字节码文件,后面我会具体的来描述。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。 举个例子,整数加法的字节码指令iadd在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,然后将相加的结果入栈。 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的iadd指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现一个long和一个float使用iadd命令相加的情况。 本地方法栈 与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 方法区经常会被人称之为永久代,但这俩并不是一个概念。首先永久代的概念仅仅在HotSpot虚拟机中存在,不幸的是,在jdk8中,Hotspot去掉了永久代这一说法,使用了Native Memory,也就是Metaspace空间。那么方法区是干嘛的呢?我们可以这么理解,我们要运行Java代码,首先需要编译,然后才能运行。在运行的过程中,我们知道首先需要加载字节码文件。也就是说要把字节码文件加载到内存中。好了,问题就来了,字节码文件放到内存中的什么地方呢,就是方法区中。当然除了编译后的字节码之外,方法区中还会存放常量,静态变量以及及时编译器编译后的代码等数据。 堆,一般来讲堆内存是Java虚拟机中最大的一块内存区域,同方法区一样,是被所有线程所共享的区域。此区域所存在的唯一目的就存放对象的实例(对象实例并不一定全部在堆中创建)。堆内存是垃圾收集器主要光顾的区域,一般来讲根据使用的垃圾收集器的不同,堆中还会划分为一些区域,比如新生代和老年代。新生代还可以再划分为Eden,Survivor等区域。另外为了性能和安全性的角度,在堆中还会为线程划分单独的区域,称之为线程分配缓冲区。更细致的划分是为了让垃圾收集器能够更高效的工作,提高垃圾收集的效率。 如果想要了解更多的关于虚拟机的内容,可以观看录制的<深入理解Java虚拟机>这套视频教程。

zwt9000 2019-12-02 00:21:07 0 浏览量 回答数 0

问题

【今日算法】备战大厂必备题目,持续更新

游客ih62co2qqq5ww 2020-04-08 09:21:40 3542 浏览量 回答数 4

问题

【精品问答】Python二级考试题库

珍宝珠 2019-12-01 22:03:38 1146 浏览量 回答数 2

回答

一、接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。 Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0 文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。 译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。 二、Lambda 表达式 首先看看在老版本的Java中是如何排列字符串的: List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } }); 只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。 在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式: Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }); 看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短: Collections.sort(names, (String a, String b) -> b.compareTo(a)); 对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点: Collections.sort(names, (a, b) -> b.compareTo(a)); Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来 三、函数式接口 Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。 我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。 @FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123 需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。 译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。 四、方法与构造函数引用 前一节中的代码还可以通过静态方法引用来表示: Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123 Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法: converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J" 接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类: class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } 接下来我们指定一个用来创建Person对象的对象工厂接口: interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } 这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂: PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker"); 我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。 五、Lambda 作用域 在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。 六、访问局部变量 我们可以直接在lambda表达式中访问外层的局部变量: final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确: int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译: int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3; 在lambda表达式中试图修改num同样是不允许的。 七、访问对象字段与静态变量 和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的: class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } } 八、访问接口的默认方法 还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。 Lambda表达式中是无法访问到默认方法的,以下代码将无法编译: Formula formula = (a) -> sqrt( a * 100); Built-in Functional Interfaces JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。 Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。 Predicate接口 Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非): Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate(); Function 接口 Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen): Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123" Supplier 接口 Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数 Supplier personSupplier = Person::new; personSupplier.get(); // new Person Consumer 接口 Consumer 接口表示执行在单个参数上的操作。 Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker")); Comparator 接口 Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0 Optional 接口 Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么: Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。 Optional optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b" Stream 接口 java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。 首先看看Stream是怎么用,首先创建实例代码的用到的数据List: List stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1"); Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作: Filter 过滤 过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。 stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1" Sort 排序 排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。 stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" 需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的。 System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1 Map 映射 中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。 stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1" Match 匹配 Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。 boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true Count 计数 计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。 long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3 Reduce 规约 这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的: Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" 并行Streams 前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。 下面的例子展示了是如何通过并行Stream来提升性能: 首先我们创建一个没有重复元素的大表 int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); } 然后我们计算一下排序这个Stream要耗时多久, 串行排序: long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // 串行耗时: 899 ms 并行排序: long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // 并行排序耗时: 472 ms 上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。 Map 前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。 Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val)); 以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。 下面的例子展示了map上的其他有用的函数: map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33 接下来展示如何在Map里删除一个键值全都匹配的项 map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null 另外一个有用的方法 map.getOrDefault(42, "not found"); // not found 对Map的元素做合并也变得很容易了: map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。 九、Date API Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分: Clock 时钟 Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。 Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date Timezones 时区 在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。 System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00] LocalTime 本地时间 LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差: LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239 LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。 LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37 LocalDate 本地日期 LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。 LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY 从字符串解析一个LocalDate类型和解析LocalTime一样简单: DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24 LocalDateTime 本地日期时间 LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。 LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439 只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。 Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式: DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13 和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。 关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html 十、Annotation 注解 在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。 首先定义一个包装类Hints注解用来放置一组具体的Hint注解: @interface Hints { Hint[] value(); } @Repeatable(Hints.class) @interface Hint { String value(); } Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。 例 1: 使用包装类当容器来存多个注解(老方法) @Hints({@Hint("hint1"), @Hint("hint2")}) class Person {} 例 2:使用多重注解(新方法) @Hint("hint1") @Hint("hint2") class Person {} 第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息: Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2 即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。 另外Java 8的注解还增加到两种新的target上了: @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}

日你dady哟 2019-12-02 03:08:13 0 浏览量 回答数 0

回答

如果对什么是线程、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内。 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。 很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程 并行与并发: 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。 并发与并行 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码: void transferMoney(User from, User to, float amount){ to.setMoney(to.getBalance() + amount); from.setMoney(from.getBalance() - amount); } 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。 好了,让我们开始吧。我准备分成几部分来总结涉及到多线程的内容: 扎好马步:线程的状态 内功心法:每个对象都有的方法(机制) 太祖长拳:基本线程类 九阴真经:高级多线程控制类 扎好马步:线程的状态 先来两张图: 线程状态 线程状态转换 各种状态一目了然,值得一提的是"blocked"这个状态:线程在Running的过程中可能会遇到阻塞(Blocked)情况 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable) 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。 此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。内功心法:每个对象都有的方法(机制) synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们 monitor 他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。 wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。 当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。 再讲用法: synchronized单独使用: 代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容 复制代码 public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } } 复制代码 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。 public class Thread1 implements Runnable { public synchronized void run() { ..do something } } synchronized, wait, notify结合:典型场景生产者消费者问题 复制代码 /** * 生产者生产出来的产品交给店员 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("产品已满,请稍候再生产"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生产者生产第" + this.product + "个产品."); notifyAll(); //通知等待区的消费者可以取出产品了 } /** * 消费者从店员取产品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺货,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消费者取走了第" + this.product + "个产品."); this.product--; notifyAll(); //通知等待去的生产者可以生产产品了 } 复制代码 volatile 多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。 volatile 针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。太祖长拳:基本线程类 基本线程类指的是Thread类,Runnable接口,Callable接口Thread 类实现了Runnable接口,启动一个线程的方法:  MyThread my = new MyThread();  my.start(); Thread类相关方法:复制代码 //当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)public static Thread.yield() //暂停一段时间public static Thread.sleep() //在一个线程中调用other.join(),将等待other执行完后才继续本线程。    public join()//后两个函数皆可以被打断public interrupte() 复制代码 关于中断:它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。Thread.interrupted()检查当前线程是否发生中断,返回booleansynchronized在获锁的过程中是不能被中断的。 中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体 Thread类最佳实践:写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。 如何获取线程中的异常 不能用try,catch来获取线程中的异常Runnable 与Thread类似Callable future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态 ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.Future future = e.submit(new myCallable());future.isDone() //return true,false 无阻塞future.get() // return 返回值,阻塞直到该线程运行结束 九阴真经:高级多线程控制类 以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。1.ThreadLocal类 用处:保存线程的独立变量。对一个线程类(继承自Thread)当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。 实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。2.原子类(AtomicInteger、AtomicBoolean……) 如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized //返回值为booleanAtomicInteger.compareAndSet(int expect,int update) 该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。此时c给b2元,但是b的加十元代码约为:复制代码 if(b.value.compareAndSet(old, value)){ return ;}else{ //try again // if that fails, rollback and log} 复制代码 AtomicReference对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号3.Lock类  lock: 在java.util.concurrent包内。共有三个实现: ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock 主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。 区别如下:复制代码 lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。本质上和监视器锁(即synchronized是一样的)能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。和Condition类的结合。性能更高,对比如下图: 复制代码 synchronized和Lock性能对比 ReentrantLock    可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。使用方法是: 1.先new一个实例 static ReentrantLock r=new ReentrantLock(); 2.加锁       r.lock()或r.lockInterruptibly(); 此处也是个不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception 或catch)     3.释放锁    r.unlock() 必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。 ReentrantReadWriteLock 可重入读写锁(读写锁的一个实现)   ReentrantReadWriteLock lock = new ReentrantReadWriteLock()  ReadLock r = lock.readLock();  WriteLock w = lock.writeLock(); 两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码4.容器类 这里就讨论比较常用的两个: BlockingQueueConcurrentHashMap BlockingQueue阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管  道,特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究 BlockingQueue在队列的基础上添加了多线程协作的功能: BlockingQueue 除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。 常见的阻塞队列有: ArrayListBlockingQueueLinkedListBlockingQueueDelayQueueSynchronousQueue ConcurrentHashMap高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap5.管理类 管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBeanThreadPoolExecutor如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:复制代码 ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e = Executors.newSingleThreadExecutor(); ExecutorService e = Executors.newFixedThreadPool(3); // 第一种是可变大小线程池,按照任务数来分配线程, // 第二种是单线程池,相当于FixedThreadPool(1) // 第三种是固定大小线程池。 // 然后运行 e.execute(new MyRunnableImpl()); 复制代码 该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc: ThreadPoolExecutor参数解释 翻译一下:复制代码 corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。maximumPoolSize:线程最大值,线程的增长始终不会超过该值。keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。 阿里云优惠券地址https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=nb3paa5b

景凌凯 2019-12-02 01:40:35 0 浏览量 回答数 0

问题

java web使用@Cacheable配置,总是报错:配置报错 

kun坤 2020-06-02 17:40:02 0 浏览量 回答数 1

问题

java web使用@Cacheable配置,总是报错?报错

爱吃鱼的程序员 2020-06-07 17:35:13 0 浏览量 回答数 1

问题

java web使用@Cacheable配置,总是报错:报错

kun坤 2020-06-20 11:33:35 1 浏览量 回答数 1

问题

java web使用@Cacheable配置,总是报错-java报错

montos 2020-06-02 13:03:20 0 浏览量 回答数 1
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站