十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
在以前的 《Flutter 上默认的文本和字体知识点》 和 《带你深入理解 Flutter 中的字体“冷”知识》 中,已经介绍了很多 Flutter 上关于字体有趣的知识点,而本篇讲继续介绍 Flutter 上关于 Text 的一个属性: FontFeature , 事实上相较于 Flutter ,本篇内容可能和前端或者设计关系更密切 。
右江网站制作公司哪家好,找创新互联!从网页设计、网站建设、微信开发、APP开发、自适应网站建设等网站项目制作,到程序开发,运营维护。创新互联成立于2013年到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联。
什么是 FontFeature ? 简单来说就是影响字体形状的一个属性 ,在前端的对应领域里应该是 font-feature-settings ,它有别于 FontFamily ,是用于指定字体内字的形状的一个参数。
我们知道 Flutter 默认在 Android 上使用的是 Roboto 字体,而在 iOS 上使用的是 SF 字体,但是其实 Roboto 字体也是分很多类型的,比如你去查阅手机的 system/fonts 目录,就会发现很多带有 Roboto 字样的字体库存在。
所以 Roboto 之类的字体库是一个很大的字体集,不同的 font-weight 其实对应着不同的 ttf ,例如默认情况下的 Roboto 是不支持 font-weight 为 600 的配置 :
所以如下图所示,如果我们设置了 w400 - w700 的 weight ,可以很明显看到中间的 500 和 600 其实是一样的粗细,所以在 设置 weight 或者设计 UI 时,就需要考虑不同平台上的 weight 是否支持想要的效果 。
回归到 FontFeature 上,那 Roboto 自己默认支持多少种 features 呢? 答案是 26 种,它们的编码如下所示,运行后效果也如下图所示,从日常使用上看,这 26 种 Feature 基本满足开发的大部分需求。
而 iOS 上的 SF pro 默认支持 39 种 Features , 它们的编码如下所示,运行后效果也如下图所示,可以看到 SF pro 支持的 Features 更多。
所以可以看到,并不是所有字体支持的 Features 都是一样的,比如 iOS 上支持 sups 上标显示和 subs 下标显示,但是 Android 上的 Roboto 并不支持,甚至很多第三方字体其实并不支持 Features 。
有趣的是,在 Flutter Web 有一个渲染文本时会变模糊的问题 #58159 ,这个问题目前官方还没有修复,但是你可以通过给 Text 设置任意 FontFeatures 来解决这个问题。
最后,如果对 FontFeature 还感兴趣的朋友,可以通过一下资料深入了解,如果你还有什么关于字体上的问题,欢迎留言讨论。
基于网友的问题再补充一下拓展知识,毕竟这方面内容也不多 。
事实上在 dart 里就可以看到对应 FontWeight 约定俗称用的是字体集里的什么字体:
所以如果对于默认字体有疑问,可以在你的手机字体找找是否有对应的字体, 比如虽然我们说 roboto 没有 600 ,但是如果是 roboto mono 字体集是有 600 的 fontweight ,甚至还有 600 斜体: 。
另外注意这是 Flutter 而不是原生,具体实现调用是在 Engine 的 paragraph_skia.cc 和 paragraph_builder_skia.cc 下对应的 setFontFamilies 相关逻辑,当然默认字体库指定在 typography.dart 下就看到,例如 'Roboto' 、 '.SF UI Display' 、 '.SF UI Text' 、 '.AppleSystemUIFont' 、 'Segoe UI' :
另外如果你在 Mac 的 Web 上使用 Flutter Web,可以看到指定的是 .AppleSystemUIFont ,而对于 .AppleSystemUIFont 它其实不算是一种字体,而是苹果上字体的一种集合别称:
[图片上传失败...(image-40f5ce-1648368234737)]
还有,如果你去看 Flutter 默认自带的 cupertino/context_menu_action.dart ,就可以看到一个有趣的情况:
当然,前面我们说了那么多,主要是针对英文的情况下,而在中文下还是有差异的 ,之前的文章也介绍过:
例如,在苹果上的简体中文其实会是 PingFang SC 字体,对应还有 PingFang TC 和 PingFang HK 的繁体集,而关于这个问题在 Flutter 上之前还出现过比较有意思的 bug :
当然后续的 #16709 修复了这个问题 ,而在以前的文章我也讲过,当时我遇到了 “Flutter 在 iOS 系统上,系统语言是韩文时,在和中文一起出现会导致字体显示异常" 的问题 :
解决方法也很简单,就是给 fontFamilyFallback 配置上 ["PingFang SC" , "Heiti SC"] 就可以了,这是因为韩文在苹果手机上使用的应该是 Apple SD Gothic Neo 这样的超集字体库,【广】这个字符在这个字体集上是不存在的,所以就变成了中文的【广】;
所以可以看到,字体相关是一个平时很少会深入接触的东西,但是一旦涉及多语言和绘制,就很容易碰到问题的领域 。
LayUI
由职业前端倾情打造,面向全层次的前后端开发者,低门槛开箱即用的前端 UI 解决方案,layui是一个采用自身模块规范化编写的前端UI框架,它依照原生HTML/CSS/JS的书写与组织形式,入门简单,使用也非常简单。从核心代码到API的每一处细节都经过精心雕琢,非常适合界面的快速开发。
JEUI
它是一款国产前端UI框架,遵循原生HTML/CSS/JS的书写与组织形式,国内很多程序员javascript不熟, 大大影响了开发速度. 因此JEUI不需要开发人员去关心javascript怎么写, 只要写标准html就可以了,门槛极低,拿来即用。其外在极简,却又不失饱满的内在,体积轻盈,组件丰盈,从核心代码到API的每一处细节都经过精心雕琢,非常适合界面的快速开发。
JEUI基于jQuery的UI框架,包括表单、布局、表格等等常用UI控件,使用JEUI可以快速轻松地创建风格统一的界面效果。
浏览器兼容非常牛皮,能兼容IE8以上的浏览器。
DWZ
DWZ富客户端框架(jQuery RIA framework), 是中国人自己开发的基于jQuery实现的Ajax RIA开源框架. 设计目标是简单实用,快速开发,降低ajax开发成本。
DWZ 支持用 html 扩展的方式来代替 javascript 代码, 基本可以保证程序员不董 javascript, 也能使用各种页面组件和 ajax 技术. 如果有特定需求也可以扩展 DWZ 做定制化开化.
MDUI
MDUI 是一个基于 Material Design 的前端框架。
19 种主色、16 种强调色、1 种夜间主题,只需添加一个 CSS 类即可切换。CSS 仅 26.7KB,JavaScript 仅 14KB,加载更迅速。移动优先,从小屏逐步扩展到大屏,最终实现所有屏幕适配。不依赖任何第三方库,节约网络流量,使加载更迅速,提高用户体验。使用 CSS3 来做动画交互,平滑、高效,让 Web 应用的动画更流畅。提供自定义打包功能,按需打包需要的主题和组件,使文件体积骤然减小。MDUI 包含了 20 余个组件,且每个组件都可以适应不同主题。
只需懂一点 HTML、CSS、JS 的基础知识,就能使用 MDUI。
ElementUI
element ui框架的按钮组件,这款由饿了么前端开源的UI框架,一经面世,就收获大量程序员的芳心,在github 上更是高达29.8k的star早已说明一切,用于开发PC端的页面还是绰绰有余的,如果说你是用vue开发者,却没用过element UI,那你肯定不是合格的vue开发者。
WeUI
jQuery WeUI 是专为微信公众账号开发而设计的一个简洁而强大的UI库,包含全部WeUI官方的CSS组件,并且额外提供了大量的拓展组件,丰富的组件库可以极大减少前端开发时间。
jQuery WeUI 的最大特点是它只提供UI组件,并不会对项目所使用的框架和其他库有任何的限制,几乎可以在任何环境下使用。无论你的项目是基于jQuery,还是 React, Angular, Vue, 你都会发现 jQuery WeUI 能非常方便的和他们结合使用。既是你的项目是一个有很悠久历史的老项目,也几乎可以做到拿来即用。
Flutter
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面,前端对于 Flutter 的热忱度之高一度让人有点惊讶,事实上在 Flutter 社区内见到的客户端开发者远多于前端开发,不过前端对于跨端解决方案确实有着天然的渴求。
Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
iView ui
iViewui一套基于 Vue.js 的高质量 UI 组件库,搭配使用 iView UI 组件库形成的一套后台集成解决方案,由 TalkingData 前端可视化团队部分成员开发维护。iView Admin 遵守 iView 设计和开发约定,风格统一。
Mint UI
Mint UI 包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要。通过它,可以快速构建出风格统一的页面,提升开发效率。
真正意义上的按需加载组件。可以只加载声明过的组件及其样式文件,无需再纠结文件体积过大。
考虑到移动端的性能门槛,Mint UI 采用 CSS3 处理各种动效,避免浏览器进行不必要的重绘和重排,从而使用户获得流畅顺滑的体验。
依托 Vue.js 高效的组件化方案,Mint UI 做到了轻量化。即使全部引入,压缩后的文件体积也仅有 ~30kb (JS + CSS) gzip。
YDUI Touch
YDUI Touch 专为移动端打造,在技术实现、交互设计上兼容主流移动设备,保证代码轻、性能高。
使用 Flex 技术,灵活自如地对齐、收缩、扩展元素,轻松搞定移动页面布局。
实现强大的屏幕适配布局,等比例适配所有屏幕。什么?用得不开心?轻松切换 px。
自定义Javascript组件、Less文件、Less变量,定制一份属于自己的YDUI。
SUI Mobile
SUI Mobile 是一套基于 Framework7 开发的UI库。它非常轻量、精美,只需要引入我们的CDN文件就可以使用,并且能兼容到 iOS 6.0+ 和 Android 4.0+,非常适合开发跨平台Web App。轻量的UI库
SUI Mobile 非常轻量,核心库压缩Gzip后的JS、CSS网络传输体积总共只有52K,却提供了20+个常用的组件。
Amaze ~ 妹子 UI
中国首个开源 HTML5 跨屏前端框架
Amaze UI 以移动优先(Mobile first)为理念,从小屏逐步扩展到大屏,最终实现所有屏幕适配,适应移动互联潮流。
Amaze UI 含近 20 个 CSS 组件、20 余 JS 组件,更有多个包含不同主题的 Web 组件,可快速构建界面出色、体验优秀的跨屏页面,大幅提升开发效率。
相比国外框架,Amaze UI 关注中文排版,根据用户代理调整字体,实现更好的中文排版效果;兼顾国内主流浏览器及 App 内置浏览器兼容支持。
Amaze UI 面向 HTML5 开发,使用 CSS3 来做动画交互,平滑、高效,更适合移动设备,让 Web 应用更快速载入。
cube-ui
质量可靠:由滴滴内部组件库精简提炼而来,历经考验,并且每个组件都有充分单元测试,为后续集成提供保障。
体验极致:以迅速响应、动画流畅、接近原生为目标,在交互体验方面追求极致。
标准规范:遵循统一的设计交互标准,高度还原设计效果;接口标准化,统一规范使用方式,开发更加简单高效。
扩展性强:支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。
hello world例子
在终端打印字符串‘Hello World!’
计算斐波那契数列
一个简单的类
计算两点距离
异步并发示例
使用了Isolate
1.面向对象
对于面向对象这个概念,相信了解Java的同学对这个概念一定不会陌生。
例如:我们有个Person Object他有很多特征和行为。
这些都是这个Person Object的属性。
也就是因为有了这些特征,行为等等才决定了这个人是谁。
那么回到Dart当中,所有的都是对象,那么在就可以可以跟进对象的属性的特征,方法等等来进行编程。
之所以我们在这里又特别强调了一下面向对象的概念,是因为这个概念在Dart语言当中,至关重要!
2.最重要的几个概念
3.Dart的部分特性
4.Dart的内置库
包名
描述
dart:asynv
异步编程,提供Future,Stream类
dart:collection
集合
dart:convert
不同类型的字符编码解码
dart:core
Dart语言核心功能,内置类型
dart:html
网页开发用到的库
dart:io
文件读写,IO相关
dart:math
数字常量以及函数,随机算法等
dart:svg
事件和动画矢量图支持
综上述所说要想学Flutter,先学Dart!关于Flutter下篇我会讲到。为什么离不开dart!
有关更多面经、核心技术笔记;自己也是从事Android开发5年有余了;整理了一些Android开发技术核心笔记和面经题纲, 如有需要的同学请私信我回复“核心笔记”或“面试”领取!
在前端项目开发过程中,现在很少有人会使用原生的CSS来搭建页面,总归都会引入一些前端UI框架以减少代码的书写。一般为了方便自己的使用,很多大公司都有自己的一套UI框架,同时也会把其开源出来。下面就是最近经常使用并且很流行的一些前端UI框架,总有一款适合你:
Mint UI
Mint UI
Mint UI是饿了么团队开发的基于Vue .js的移动端UI框架,它包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要。
WeUI
WeUI是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含button、cell、dialog、toast、article、icon等各式元素。
Cube-ui
Cube-ui
Cube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库。支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。
iView UI
iView UI
iView UI是一个强大的UI库,基于vue,有很多实用的基础组件比elementui的组件更丰富,主要服务于 PC 界面的中后台产品。使用单文件的 Vue 组件化开发模式 基于 npm + webpack + babel 开发,支持 ES2015 高质量、功能丰富 友好的 API ,自由灵活地使用空间。
LayUI
LayUI
LayUI是一款采用自身模块规范编写的前端 UI 框架,遵循原生 HTML/CSS/JS 的书写与组织形式,门槛极低,拿来即用。其外在极简,却又不失饱满的内在,体积轻盈,组件丰盈,从核心代码到 API 的每一处细节都经过精心雕琢,非常适合界面的快速开发。
ElementUI
ElementUI
Element是饿了么前端开源维护的Vue UI组件库,组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。 主要用于开发PC端的页面,是一个质量比较高的Vue UI组件库。
at-ui
at-ui
at-ui 是一款阿里团队创建的基于 Vue 2.x 的前端 UI 组件库,主要用于快速开发 PC 网站产品。 它提供了一套 npm + webpack + babel 前端开发工作流程,CSS 样式独立,即使采用不同的框架实现都能保持统一的 UI 风格。
amaze UI
amaze UI
Amaze UI 是一个移动优先的跨屏前端框架。提供基础样式,网格,表格、表单、按钮及常用组件样式。是一个轻量级(所有 CSS 和 JS gzip 后 100 kB 左右)、 Mobile first 的前端框架
Vant UI
Vant UI
Vant UI是有赞前端团队基于有赞统一的规范实现的 Vue 组件库,提供了一整套 UI 基础组件和业务组件。通过 Vant,可以快速搭建出风格统一的页面,提升开发效率。
Flutter
Flutter
Flutter 是谷歌的移动端 UI 框架,可在极短的时间内构建 Android 和 iOS 上高质量的原生级应用。 Flutter 可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且 Flutter 是免费和开源的.
ionic
Ionic既是一个CSS框架也是一个Javascript UI库,Ionic 是目前最有潜力的一款 HTML5 手机应用开发框架。通过 SASS 构建应用程序,它 提供了很多 UI 组件来帮助开发者开发强大的应用。 它使用 JavaScript MVVM 框架和 AngularJS 来增强应用。提供数据的双向绑定,使用它成为 Web 和移动开发者的共同选择。
想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。
Java内存模型规定了 所有的变量都存储在主内存中 。 每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来) 。 线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成 。
基于此种内存模型,便产生了多线程编程中的数据“脏读”等问题。
举个简单的例子:在java中,执行下面这个语句:
i = 10;
执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。而不是直接将数值10写入主存当中。
比如同时有2个线程执行这段代码,假如初始时i的值为10,那么我们希望两个线程执行完之后i的值变为12。但是事实会是这样吗?
可能存在下面一种情况:初始时,两个线程分别读取i的值存入各自所在的工作内存当中,然后线程1进行加1操作,然后把i的最新值11写入到内存。此时线程2的工作内存当中i的值还是10,进行加1操作之后,i的值为11,然后线程2把i的值写入内存。
最终结果i的值是11,而不是12。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。
那么如何确保共享变量在多线程访问时能够正确输出结果呢?
在解决这个问题之前,我们要先了解并发编程的三大概念: 原子性,有序性,可见性 。
1.定义
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2.实例
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。
试想一下,如果这2个操作不具备原子性,会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。
所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
同样地反映到并发编程中会出现什么结果呢?
举个最简单的例子,大家想一下假如为一个32位的变量赋值过程不具备原子性的话,会发生什么后果?
假若一个线程执行到这个语句时,我暂且假设为一个32位的变量赋值包括两个过程:为低16位赋值,为高16位赋值。
那么就可能发生一种情况:当将低16位数值写入之后,突然被中断,而此时又有一个线程去读取i的值,那么读取到的就是错误的数据。
3.Java中的原子性
在Java中, 对基本数据类型的变量的读取和赋值操作是原子性操作 ,即这些操作是不可被中断的,要么执行,要么不执行。
上面一句话虽然看起来简单,但是理解起来并不是那么容易。看下面一个例子i:
请分析以下哪些操作是原子性操作:
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
咋一看,可能会说上面的4个语句中的操作都是原子性操作。其实只有语句1是原子性操作,其他三个语句都不是原子性操作。
语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存 ,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的, x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值 。
所以上面4个语句只有语句1的操作具备原子性。
也就是说, 只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作, 如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
1.定义
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
2.实例
举个简单的例子,看下面这段代码:
//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;
由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到工作内存中,然后赋值为10,那么在线程1的工作内存当中i的值变为10了,却没有立即写入到主存当中。
此时线程2执行 j = i,它会先去主存读取i的值并加载到线程2的工作内存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.
这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。
3.Java中的可见性
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性, 因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且 在释放锁之前会将对变量的修改刷新到主存当中 。因此可以保证可见性。
1.定义
有序性:即程序执行的顺序按照代码的先后顺序执行。
2.实例
举个简单的例子,看下面这段代码:
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。
下面解释一下什么是指令重排序, 一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。
但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
这段代码有4个语句,那么可能的一个执行顺序是:
那么可不可能是这个执行顺序呢: 语句2 语句1 语句4 语句3
不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。
虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:
上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。
从上面可以看出, 指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
也就是说, 要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
3.Java中的有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
另外,Java内存模型具备一些先天的“有序性”, 即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
下面就来具体介绍下happens-before原则(先行发生原则):
①程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
②锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
③volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
④传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
⑤线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
⑥线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
⑦线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
⑧对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。
下面我们来解释一下前4条规则:
对于程序次序规则来说,就是一段程序代码的执行 在单个线程中看起来是有序的 。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的, 但是虚拟机可能会对程序代码进行指令重排序 。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此, 在单个线程中,程序执行看起来是有序执行的 ,这一点要注意理解。事实上, 这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
第二条规则也比较容易理解,也就是说无论在单线程中还是多线程中, 同一个锁如果处于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。
第三条规则是一条比较重要的规则。直观地解释就是, 如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。
第四条规则实际上就是体现happens-before原则 具备传递性 。
1.volatile保证可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了 不同线程对这个变量进行操作时的可见性 ,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2) 禁止进行指令重排序。
先看一段代码,假如线程1先执行,线程2后执行:
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会 强制将修改的值立即写入主存 ;
第二:使用volatile关键字的话,当线程2进行修改时, 会导致线程1的工作内存中缓存变量stop的缓存行无效 (反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以 线程1再次读取变量stop的值时会去主存读取 。
那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
那么线程1读取到的就是最新的正确的值。
2.volatile不能确保原子性
下面看一个例子:
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。
这里面就有一个误区了, volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。 可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
在前面已经提到过, 自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存 。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
假如某个时刻变量inc的值为10,
线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了 ;
然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值, 由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,也不会导致主存中的值刷新, 所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
那么两个线程分别进行了一次自增操作后,inc只增加了1。
根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。
解决方案:可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger。
在java 1.5的java.util.concurrent.atomic包下提供了一些 原子操作类 ,即对基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作。 atomic是利用CAS来实现原子性操作的(Compare And Swap) ,CAS实际上是 利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作。
3.volatile保证有序性
在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时, 在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行 ;
2)在进行指令优化时, 不能将在对volatile变量的读操作或者写操作的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
可能上面说的比较绕,举个简单的例子:
由于 flag变量为volatile变量 ,那么在进行指令重排序的过程的时候, 不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
并且volatile关键字能保证, 执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
那么我们回到前面举的一个例子:
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
前面举这个例子的时候,提到有可能语句2会在语句1之前执行,那么久可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。
这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了, 因为当执行到语句2时,必定能保证context已经初始化完毕。
1.可见性
处理器为了提高处理速度,不直接和内存进行通讯,而是将系统内存的数据独到内部缓存后再进行操作,但操作完后不知什么时候会写到内存。
2.有序性
Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保 指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面; 即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
下面列举几个Java中使用volatile的几个场景。
①.状态标记量
volatile boolean flag = false;
//线程1
while(!flag){
doSomething();
}
//线程2
public void setFlag() {
flag = true;
}
根据状态标记,终止线程。
②.单例模式中的double check
为什么要使用volatile 修饰instance?
主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?
没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【 包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料 】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
使用注解@JSONField
其中name: "list"的list就是后台返回字段名称,deserialize(默认true)是否参与fromJson解析,serialize(默认true)是否参与tojson,
比如包含如下json
可以解析出来list中map的所有字段,并且每个list的map字段不同或者为null问题有会做出处理
helper文件内容
直接传递上面生成的entity就可以自动根据map解析出对应实例,并自动赋值
网络请求实例
dio请求部分
这些操作完成后自动生成如下文件
@JSONField作用在Field时,其name不仅定义了输入key的名称,为了防止后台返回数据不规范,但是flutter端需要按照驼峰命名
a_b_c_entity_helper.dart类提供了eitity类的tojson和fromjson代理方法
json_convert_content.dart提供了json_convert_content.dart.fromJsonAsT方法 根据泛型来解析json成对象