首页 文章 精选 留言 我的

精选列表

搜索[基础搭建],共10000篇文章
优秀的个人博客,低调大师

(一)Java工程化--Maven基础

Maven 读作['mevən] 翻译成中文是"内行,专家" Maven是什么 包依赖的前世今生: 原始的jar包引用--> ant --> maven. 是一种项目管理工具 Maven优势: convertion over configuration 约定优于配置: 这个原则不仅适用于maven, 更是目前大多数框架遵循的原则,如mvc 简单 易于测试 构建简单 CI(持续集成) 插件丰富 下载和安装 下载: http://maven.apache.org/download.cgi 安装: 解压安装即可 环境变量配置: windows: 配置path MAVEN_HOME linux: 在 .bash_profile 文件中 运行参数: 定义环境变量MAVEN_OPS 配置settings.xml: settings.xml文件是个空模板,我们可以在这个文件进行一些自定义配置. 常用的配置如:repo存储目录 创建maven项目 项目结构: 遵循约定优于配置原则, 项目包的组织结构如下 pom.xml groupId 公司组织id artifactId 功能命名 version 版本号 packageing 打包方式,默认jar,可修改为maven-project,war dependancyManagement 最好之出现在父pom中,用于统一版本号,只做声明依赖,子模块pom中还需要引用,但不需要制定version. dependancy type 默认jar scope 指定哪个阶段适用,各阶段如下: compile 编译,打包,默认 如spring-core test 测试 如spring-test provided 编译 如servlet runtime 运行时 如JDBC驱动实现包 system 本地一些jar 依赖传递 依赖仲裁:(1)最短路径原则,根据依赖树就近取最接近的版本;(2)加载顺序原则;(3)exclusions 排除包 命令: mvn dependancy:tree Maven生命周期(三个过程) 区分术语:lifecycle/phase/goal 生命周期: clean 包含的phase: pre-clean --> clean --> post-clean default compile package install deploy ... site pre-site --> site -->post-site --> site-deploy A Build Lifecycle is Made Up of Phases 一个构建生命周期是有多个phase组成 A Build Phase is Made Up of Plugin Goals 一个构建phase是由多个插件目标goal构成

优秀的个人博客,低调大师

Python编程入门基础语法详解经典

一、基本概念 1.内置的变量类型: Python是有变量类型的,而且会强制检查变量类型。内置的变量类型有如下几种: #浮点 float_number=2.3 #复数 complex_number=1+5j #整型 integer_number=1 #list 序列 sample_list=[2,52,36,'abc'] #嵌套 sample_nest=[(2,4,6),{5:7,9:11,'key':[2,5]},6] #dictionary 字典 sample_dic={"key":value, 5:10} #tuple 只读的序列 sample_tuple=(3,9,"ab") 从上面的示例可以看出,python的变量无需声明,可直接给变量赋值。 2.字符串 python中声明一个字符串通常有三种方法,''、" "和''' ''',这三种方法在声明普通字符串时的效果是完全一样的,区别在于字符串本身中存在引号的情况,举例如下: word='good' sentence="hello world" paragraph='''good noon:"nice to meet you."''' python中单行注释采用#开头。 #!/usr/bin/python # First comment print "Hello, world!"; # second comment 上述内容输出:hello,Python!。注释可以在语句或表达式行末。 多行注释可采用三引号,比如: '''This is a comment. This is a comment, too. This is a comment, too. I said that already.''' 转义符 ''; 自然字符串, 通过在字符串前加r或R。 如 r"this is a line with " 则 会显示,并不是换行。 python允许处理unicode字符串,加前缀u或U, 如 u"this is an unicode string"。 3.Python标识符 在python里,标识符有字母、数字、下划线组成。 在python中,所有标识符可以包括英文、数字以及下划线(_),但不能以数字开头。 python中的标识符是区分大小写的。 以下划线开头的标识符是有特殊意义的。以单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用"from xxx import *"而导入; 以双下划线开头的(__foo)代表类的私有成员;以双下划线开头和结尾的(__foo__)代表python里特殊方法专用的标识,如__init__()代表类的构造函数。 4.Python保留字符 下面的列表显示了在Python中的保留字。这些保留字不能用作常数或变数,或任何其他标识符名称。 所有Python的关键字只包含小写字母。 如果你在学习Python的过程中遇见了很多疑问和难题,可以加-q-u-n227 -435-450里面有软件视频资料免费 5.行和缩进 学习Python与其他语言最大的区别就是,Python的代码块不使用大括号({})来控制类,函数以及其他逻辑判断。python最具特色的就是用缩进来写模块。 缩进的空白数量是可变的,但是所有代码块语句必须包含相同的缩进空白数量,这个必须严格执行。如下所示: ifTrue: print"True" else: print"False" 上述,if与else的缩进空白数量相同。以下代码将会报错: ifTrue: print"Answer" print "True" else: print "Answer" print"False" python中分号;标识一个逻辑行的结束,但是实际中一般每个物理行只写一个逻辑行,可以避免使用分号。这样书写便于阅读。注意:不要混合使用空格和制表符来缩进,因为在跨越不同的平台时无法正常工作。 多个物理行中可以写一个逻辑行,使用行连接符,如下: s="peteris writing this article" 二、运算符与表达式 1. 运算符与其用法 2. 运算符优先级(从低到高) 三.控制流 1. 条件语句 1.1 if语句 例子, 执行if语句内的程序 a=input("a:") b=input("b:") if(a > b): print a, " > ", b if else语句: a=input("a:") b=input("b:") if(a > b): print a, " > ", b else: print a, " < ", b 1.2 if…elif…else语句 例子:根据输入的分数,输出分数等级: score=raw_input("score:") score=int(score) if(score >=90)and(score <=100): print"A" elif(score >=80)and(score < 90): print"B" elif(score >=60)and(score < 80): print "C" else: print "D" raw_input() 读取输入值。 1.3 if语句的嵌套 编写条件语句时,应该尽量避免使用嵌套语句。嵌套语句不便于阅读,而且可能会忽略一些可能性。 x=-1 y=99 if(x >=0): if(x > 0): #嵌套的if语句 y=1 else: y=0 else: y=-1 print "y =", y 1.4 实现switch语句的功能 python中没有switch特殊字,Python可以通过字典实现switch语句的功能。 实现方法分两步。首先,定义一个字典。字典是由键值对组成的集合。其次,调用字典的get()获取相应的表达式。 from__future__importdivision x=1 y=2 operator="/" result={ "+" : x+y, "-" : x-y, "*" : x*y, "/" : x/y } print result.get(operator) 输出为0.5; 另一种使用switch分支语句的方案是创建一个switch类,处理程序的流程。 a) 创建一个switch类,该类继承自Python的祖先类object。调用构造函数init( )初始化需要匹配的字符串,并需要定义两个成员变量value和fall。Value用于存放需要匹配的字符串,fall用于记录是否匹配成功,初始值为false,标识匹配不成功。如果匹配成功,程序往后执行。 b) 定义一个match( )方法,该方法用于用于匹配case子句。这里需要考虑三种情况:首先是匹配成功的情况,其次是匹配失败的默认case子句,最后是case子句中没有使用break中断的情况。 c) 重写__iter__( )方法,定义该方法后才能使switch类用于循环语句中。__iter__( )调用match( )方法进行匹配。通过yield保留字,使函数可以在循环中迭代。此外,调用StopIteration异常中断循环。 d) 编写调用代码,在for…in…循环中使用switch类。 #!/usr/bin/python # -*- coding: UTF-8 -*- classswitch(object): def__init__(self, value): # 初始化需要匹配的值value self.value=value self.fall=False # 如果匹配到的case语句中没有break,则fall为true。 def__iter__(self): yieldself.match # 调用match方法 返回一个生成器 raiseStopIteration # StopIteration 异常来判断for循环是否结束 defmatch(self,*args): # 模拟case子句的方法 ifself.fallornotargs: # 如果fall为true,则继续执行下面的case子句 # 或case子句没有匹配项,则流转到默认分支。 returnTrue elifself.valueinargs: # 匹配成功 self.fall=True returnTrue else: # 匹配失败 returnFalse operator="+" x=1 y=2 forcaseinswitch(operator): # switch只能用于for in循环中 ifcase('+'): print x+y break ifcase('-'): printx-y break ifcase('*'): printx*y break ifcase('/'): printx/y break ifcase(): # 默认分支 print"" 2.while...语句 只要在一个条件为真的情况下,while语句允许你重复执行一块语句。while语句是所谓 循环 语句的一个例子。while语句有一个可选的else从句。 whileTrue: pass else: pass #else语句可选,当while为False时,else语句被执行。 pass是空语句。 3.for 循环 foriinrange(0, 5): print i else: pass # 打印0到4 注:当for循环结束后执行else语句;range(a, b)返回一个序列,从a开始到b为止,但不包括b,range默认步长为1,可以指定步长,range(0,10,2); 四、函数 函数通过def定义。def关键字后跟函数的标识符名称,然后跟一对圆括号,括号之内可以包含一些变量名,该行以冒号结尾;接下来是一块语句,即函数体。 defsumOf(a, b): returna+b 4.1 局部变量 在函数内定义的变量与函数外具有相同名称的其他变量没有任何关系,即变量名称对于函数来说是局部的。这称为变量的作用域。global语句, 为定义在函数外的变量赋值时使用global语句。 deffunc(): globalx print "x is ", x x=1 x=3 func() printx 以上代码,输出的结果为: 3 1 4.2 默认参数 通过使用默认参数可以使函数的一些参数是‘可选的’。 defsay(msg, times=1): print msg*times say("peter") say("peter", 3) 注意:只有在形参表末尾的那些参数可以有默认参数值,即不能在声明函数形参的时候,先声明有默认值的形参而后声明没有默认值的形参,只是因为赋给形参的值是根据位置而赋值的。 4.3 关键参数 如果某个函数有很多参数,而现在只想指定其中的部分,那么可以通过命名为这些参数赋值(称为‘关键参数’)。 优点:不必担心参数的顺序,使函数变的更加简单;假设其他参数都有默认值,可以只给我们想要的那些参数赋值。 deffunc(a, b=2, c=3): print"a is %s, b is %s, c is %s"%(a, b, c) func(1) #输出a is 1, b is 2, c is 3 func(1, 5) #输出a is 1, b is 5, c is 3 func(1, c=10) #输出a is 1, b is 2, c is 10 func(c=20, a=30) #输出a is 30, b is 2, c is 20 4.3 return语句 return语句用来从一个函数返回,即跳出函数。可从函数返回一个值。 没有返回值的return语句等价于return None。None表示没有任何东西的特殊类型。

优秀的个人博客,低调大师

Java基础-Java的语法规范

1.Java中的语法大小写区分,举例:(int H;h=10)在声明变量时用大写,赋值却用的小写的他是不认的,还有关键字,大小写区分要不然不认的。 2.括号成对出现:举例写的时候将格式书写完整{}[](); 3.java中的语法符号都是半角符号:因为全角符号占2个字节 半角符号只占1个字节( ; ;)跟语法相关的都是半角符号。 4.Java跟C语言的语法很像:每句代码要有分号结束,因为Java模仿C语言; 5.命名规范;不能含有的字符很多 , 字母或下划线开头可以用数字结尾(变量名,类名,方法名,属性名)。 6.书写代码要有良好的代码书写格式手法(结构写完整,然后填充内容)会很少会发生语法上的错误。 7.备份:每天要学会备份,不备份存在隐患,尽量上传到服务器上去,上传到服务器上的好处,不存在丢除非服务区死掉。 代码越写路越窄:备份了的话可以回头看,回到以前看看,可以调整思路。 8.类的命名规范:里面如果有一个用public修饰的class那他的名称必须和文件名称一致。 一个文件文件可以申请多个class吗?:一个文件可以申请多个class文件 一个文件不能含有多个public class声明。 正确写法:a.java 文件名必须一致 public class a{ } class b{ } class c{ } class d{ } 上面编译以后会生成3个文件; a.class b.class c.class d.class 写的是写在过括号外面的。 一个文件最好声明一个class,不要写多个,多个容易出错,查找起来也麻烦,有可能会起冲突。class(有可能会重名,起冲突)

优秀的个人博客,低调大师

javascript基础修炼(3)—What's this(下)

开发者的javascript造诣取决于对【动态】和【异步】这两个词的理解水平。 这一期主要分析各种实际开发中各种复杂的this指向问题。 一. 严格模式 严格模式是ES5中添加的javascript的另一种运行模式,它可以禁止使用一些语法上不合理的部分,提高编译和运行速度,但语法要求也更为严格,使用use strict标记开启。 严格模式中this的默认指向不再为全局对象,而是默认指向undefined。这样限制的好处是在使用构造函数而忘记写new操作符时会报错,而不会把本来需要绑定在实例上的一堆属性全绑在window对象上,在许多没有正确地绑定this的场景中也会报错。 二. 函数和方法的嵌套与混用 词法定义并不影响this的指向 , 因为this是运行时确定指向的。 2.1 函数定义的嵌套 function outerFun(){ function innerFun(){ console.log('innerFun内部的this指向了:',this); } innerFun(); } outerFun(); 控制台输出的this指向全局对象。 2.2 对象属性的嵌套 当调用的函数在对象结构上的定义具有一定深度时,this指向这个方法所在的对象,而不是最外层的对象。 var IronMan = { realname:'Tony Stark', rank:'1', ability:{ total_types:100, fly:function(){ console.log('IronMan.ability.fly ,作为方法调用时this指向:',this); }, } } IronMan.ability.fly(); 控制台输出的this指向IronMan的ability属性所指向的对象,调用fly( )这个方法的对象是IronMan.ability所指向的对象,而不是IronMan所指向的对象。 this作为对象方法调用时,标识着这个方法是如何被找到的。IronMan这个标识符指向的对象信息并不能在运行时找到fly( )这个方法的位置,因为ability属性中只存了另一个对象的引用地址,而IronMan.ability对象的fly属性所记录的指向,才能让引擎在运行时找到这个匿名方法。 三. 引用转换 引用转换实际上并不会影响this的指向,因为它是词法性质的,发生在定义时,而this的指向是运行时确定的。只要遵循this指向的基本原则就不难理解。 3.1 标识符引用转换为对象方法引用 var originFun = function (){ console.log('originFun内部的this为:',this); } var ironMan = { attack:originFun }; ironMan.attack(); 这里的this指向其调用者,也就是ironMan引用的对象。 3.2 对象方法转换为标识符引用 var ironMan = { attack:function(){ console.log('对象方法中this指向了:',this); } } var originFun = ironMan.attack; originFun(); 这里的this指向全局对象,浏览器中也就是window对象。3.2中的示例被认为是javascript语言的bug,即this指向丢失的问题。同样的问题也可能在回调函数传参时发生,本文【第5章】将对这种情况进行详细说明。 四. 回调函数 javascript中的函数是可以被当做参数传递进另一个函数中的,也就有了回调函数这样一个概念。 4.1 this在回调函数中的表现 var IronMan = { attack:function(findEnemy){ findEnemy(); } } function findEnemy(){ console.log('已声明的函数被当做回调函数调用,this指向:',this); } var attackAction = { findEnemy:function(){ console.log('attackAction.findEnemy本当做回调函数调用时,this指向',this); }, isArmed:function(){ console.log('check whether the actor is Armed'); } } //1.直接传入匿名函数 IronMan.attack(function(){ console.log(this); }); //2.传入外部定义函数 IronMan.attack(findEnemy); //3.传入外部定义的对象方法 IronMan.attack(attackAction.findEnemy); 从控制台打印的结果来看,无论以哪种方式来传递回调函数,回调函数执行时的this都指向了全局变量。 4.2 原理 javascript中函数传参全部都是值传递,也就是说如果调用函数时传入一个原始类型,则会把这个值赋值给对应的形参;如果传入一个引用类型,则会把其中保存的内存指向的地址赋值给对应的形参。所以在函数内部操作一个值为引用类型的形参时,会影响到函数外部作用域,因为它们均指向内存中的同一个函数。详细可参考[深入理解javascript函数系列第二篇——函数参数]这篇博文。 理解了函数传参,就很容易理解回调函数中this为何指向全局了,回调函数对应的形参是一个引用类型的标识符,其中保存的地址直接指向这个函数在内存中的真实位置,那么通过执行这个标识符来调用函数就等同于this基本指向规则中的作为函数来调用的情况,其this指向全局对象也就不难理解了。 五. this指针丢失 在第三节和第四节中,通过原理分析就能够明白为何在一些特定的场合下this会指向全局对象,但是从语言的角度来看,却很难理解this为什么指向了全局对象,因为这个规则和语法的字面意思是有冲突的。 5.1 回调函数的字面语境 var name = 'HanMeiMei'; var liLei = { name:'liLei', introduce:function () { console.log('My name is ', this.name); } }; var liLeiSay = liLei.introduce; liLeiSay();//同第三节中的引用转换示例 setTimeout(liLei.introduce,2000);//同第四节中的回调函数示例 上面的代码从字面上看意义是很明确的,就是希望liLei立刻介绍一下自己,在2秒后再介绍一下他自己。但控制台输出的结果中,他却两次都说自己的名字是HanMeiMei。 5.2 this指针丢失 5.1中的示例,也称为this指针丢失问题,被认为是Javascript语言的设计失误,因为这种设计在字面语义上造成了混乱。 5.3 this指针修复 方式1-使用bind 为了使代码的字面语境和实际执行保持一致,需要通过显示指定this的方式对this的指向进行修复。常用的方法是使用bind( )生成一个确定了this指向的新函数,将上述示例改为如下方式即可修复this的指向: var liLeiSay = liLei.introduce.bind(liLei); setTimeout(liLei.introduce.bind(liLei),2000); bind( )的实现其实并不复杂,是闭包实现高阶函数的一个简单的实例,感兴趣的读者可以自行了解。 方式2-使用Proxy Proxy是ES6中才支持的方法。 //绑定This的函数 function fixThis (target) { const cache = new WeakMap(); //返回一个新的代理对象 return new Proxy(target, { get (target, key) { const value = Reflect.get(target, key); //如果要取的属性不是函数,则直接返回属性值 if (typeof value !== 'function') { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }); } const toggleButtonInstance = fitThis(new ToggleButton()); 两种修复this指向的思路其实很类似,第一种方式相当于为调用的方法创建了一个代理方法,第二种方式是为被访问的对象创建了一个代理对象。 六. this的透传 实际开发过程中,往往需要在更深层次的函数中获取外层this的指向。 常规的解决方案是:将外层函数的this赋值给一个局部变量,通会使用_this,that,self,_self等来作为变量名保存当前作用域中的this。由于在javascript中作用域链的存在,嵌套的内部函数可以调用外部函数的局部变量,标识符会去寻找距离作用域链末端最近的一个指向作为其值,示例如下: document.querySelector('#btn').onclick = function(){ //保存外部函数中的this var _this = this; _.each(dataSet, function(item, index){ //回调函数的this指向了全局,调用外部函数的this来操作DOM元素 _this.innerHTML += item; }); } 七. 事件监听 事件监听中this的指向情况其实是几种情况的集合,与代码如何编写有很大关系。 7.1 表现 1. 在html文件中使用事件监听相关的属性来触发方法 <button onclick="someFun()">点击按钮</button> <button onclick="someObj.someFun()">点击按钮</button> 如果以第一种方式触发,则函数中的this指向全局; 如果以第二种方式触发,则函数中的this指向someObj这个对象。 2. 在js文件中直接为属性赋值 //声明一个函数 function callFromHTML() { console.log('callFromHTML,this指向:',this); } //定义一个对象方法 var obj = { callFromObj:function () { console.log('callFromObj',this); } } //注册事件监听-方式1 document.querySelector('#btn').onclick = function (event) { console.log(this); } //注册事件监听-方式2 document.querySelector('#btn').onclick = callFromHTML; //注册事件监听-方式3 document.querySelector('#btn').onclick = obj.callFromObj; 以上三种注册的事件监听响应函数,其this均指向id="btn"的DOM元素。 3. 使用addEventListener方法注册响应函数 //低版本IE浏览器中需要使用另外的方法 document.querySelector('#btn').addEventListener('click',function(event){ console.log(this); }); //也可以将函数名或对象方法作为回调函数传入 document.querySelector('#btn').addEventListener('click',callFromHTML); document.querySelector('#btn').addEventListener('click',obj.callFromObj); 这种方式注册的响应函数,其this与场景2相同,均指向id="btn"的DOM元素。区别在于使用addEventListener方法添加的响应函数会依次执行,而采用场景2的方式时,只有最后一次赋值的函数会被调用。 7.2 基本原理 1. 通过标签属性注册 <button id="btn" onclick="callFromHTML()">点我</button> <script> function callFromHTML() { console.log(document.querySelector('#btn').onclick); } </script> 在html中绑定事件处理程序,然后当按钮点击时,在控制台打印出DOM对象的onclick属性,可以看到: 这种绑定方式其实是将监听方法包裹在另一个函数中去执行,相当于: document.querySelector('#btn').onclick = function(event){ callFromHTML(); } 这样上述的表现就不难理解了。 2. 通过元素对象属性注册 document在javascript中是一个对象,通过其暴露的查找方法返回的节点也是一个对象,那么方式二绑定的监听函数在运行时,实际上就是在执行指定节点的onclick方法,根据this指向的基本规则可知其函数体中的this应该指向调用对象,也就是onclick这个方法所在的节点对象。 3. 通过addEventListener方法注册 这种方式是在DOM2事件模型中扩展的,用于支持多个监听器绑定的场景。DOM2事件模型的描述中规定了通过这种方式添加的监听函数执行时的this指向所在的节点对象,不同内核的浏览器实现方式有区别。 7.3 使用建议 不同的使用方式实质上是伴随着DOM事件模型升级而发生改变的,现代浏览器对于以上几种模式都是支持的,只有需要兼容老版本浏览器时需要考虑对DOM事件模型的支持程度。开发中DOM2级事件模型中addEventListener()和removeEventListener()来管理事件监听函数是最为推荐的方法。 八. 异步函数 1. setTimeout( )和setInterval( ) 这里的情况相当于上文中的回调函数的情况。 2. 事件监听 详见第7章。 3. ajax请求 几乎没有遇到过。 4. Promise 这里的情况相当于上文中的回调函数的情况。 九. 箭头函数和this 箭头函数是ES6标准中支持的语法,它的诞生不仅仅是因为表达方式简洁,也是为了更好地支持函数式编程。箭头函数内部不绑定this,arguments,super,new.target,所以由于作用域链的机制,箭头函数的函数体中如果使用到this,则执行引擎会沿着作用域链去获取外层的this。 十. Nodejs中的this Nodejs是一种脱离浏览器环境的javascript运行环境,this的指向规则上与浏览器环境在全局对象的指向上存在一定差异。 1. 全局对象global Nodejs的运行环境并不是浏览器,所以程序里没有DOM和BOM对象,Nodejs中也存在全局作用域,用来定义一些不需要通过任何模块的加载即可使用的变量、函数或类,全局对象中多为一些系统级的信息或方法,例如获取当前模块的路径,操作进程,定时任务等等。 2. 文件级this指向 Nodejs是支持模块作用域的,每一个文件都是一个模块,可通过require( )的方式同步引入,通过module.exports来暴露接口供其他模块调用。在一个文件中最顶级的this指向当前这个文件模块对外暴露的接口对象,也就是module.exports指向的对象。示例: var IronMan = { name:'Tony Stark', attack: function(){ } } exports.IronMan = IronMan; console.log(this); 在控制台即可看到,this指向一个对象,对象中只有一个属性IronMan,属性值为文件中定义的IronMan这个对象。 3. 函数级this指向 this的基本规则中有一条—当作为函数调用时,函数中的this指向全局对象,这一条在nodejs中也是成立的,这里的this指向了全局对象(此处的全局对象Global对象是有别于模块级全局对象的)。 思考题— React组件中为什么要bind(this) 如果你尝试使用过React进行前端开发,一定见过下面这样的代码: //假想定义一个ToggleButton开关组件 class ToggleButton extends React.Component{ constructor(props){ super(props); this.state = {isToggleOn: true}; this.handleClick = this.handleClick.bind(this); this.handleChange = this.handleChange.bind(this); } handleClick(){ this.setState(prevState => ({ isToggleOn: !preveState.isToggleOn })); } handleChange(){ console.log(this.state.isToggleOn); } render(){ return( <button onClick={this.handleClick} onChange={this.handleChange}> {this.state.isToggleOn ? 'ON':'OFF'} </button> ) } } 思考题:构造方法中为什么要给所有的实例方法绑定this呢?(强烈建议读者先自己思考再看笔者分析) 1. 代码执行的细节 上例仅仅是一个组件类的定义,当在其他组件中调用或是使用ReactDOM.render( )方法将其渲染到界面上时会生成一个组件的实例,因为组件是可以复用的,面向对象的编程方式非常适合它的定位。根据this指向的基本规则就可以知道,这里的this最终会指向组件的实例。 组件实例生成的时候,构造器constructor会被执行,此处着重分析一下下面这行代码: this.handleClick = this.handleClick.bind(this); 此时的this指向新生成的实例,那么赋值语句右侧的表达式先查找this.handleClick( )这个方法,由对象的属性查找机制(沿原型链由近及远查找)可知此处会查找到原型方法this.handleClick( ),接着执行bind(this),此处的this指向新生成的实例,所以赋值语句右侧的表达式计算完成后,会生成一个指定了this的新方法,接着执行赋值操作,将新生成的函数赋值给实例的handleClick属性,由对象的赋值机制可知,此处的handleClick会直接作为实例属性生成。总结一下,上面的语句做了一件这样的事情: 把原型方法handleClick( )改变为实例方法handleClick( ),并且强制指定这个方法中的this指向当前的实例。 2. 绑定this的必要性 在组件上绑定事件监听器,是为了响应用户的交互动作,特定的交互动作触发事件时,监听函数中往往都需要操作组件某个状态的值,进而对用户的点击行为提供响应反馈,对开发者来说,这个函数触发的时候,就需要能够拿到这个组件专属的状态合集(例如在上面的开关组件ToggleButton例子中,它的内部状态属性state.isToggleOn的值就标记了这个按钮应该显示ON或者OFF),所以此处强制绑定监听器函数的this指向当前实例的也很容易理解。 React构造方法中的bind会将响应函数与这个组件Component进行绑定以确保在这个处理函数中使用this时可以时刻指向这一组件的实例。 3. 如果不绑定this 如果类定义中没有绑定this的指向,当用户的点击动作触发this.handleClick( )这个方法时,实际上执行的是原型方法,可这样看起来并没有什么影响,如果当前组件的构造器中初始化了state这个属性,那么原型方法执行时,this.state会直接获取实例的state属性,如果构造其中没有初始化state这个属性(比如React中的UI组件),说明组件没有自身状态,此时即使调用原型方法似乎也没什么影响。 事实上的确是这样,这里的bind(this)所希望提前规避的,就是第五章中的this指针丢失的问题。 例如使用解构赋值的方式获取某个属性方法时,就会造成引用转换丢失this的问题: const toggleButton = new ToggleButton(); import {handleClick} = toggleButton; 上例中解构赋值获取到的handleClick这个方法在执行时就会报错,Class的内部是强制运行在严格模式下的,此处的this在赋值中丢失了原有的指向,在运行时指向了undefined,而undefined是没有属性的。 另一个存在的限制,是没有绑定this的响应函数在异步运行时可能会出问题,当它作为回调函数被传入一个异步执行的方法时,同样会因为丢失了this的指向而引发错误。 如果没有强制指定组件实例方法的this,在将来的使用中就无法安心使用引用转换或作为回调函数传递这样的方式,对于后续使用和协作开发而言都是不方便的。 参考 [1]《javascript高级程序设计(第三版)》 [2]《深入理解javascript函数系列第二篇》https://www.cnblogs.com/xiaohuochai/p/5706289.html [3]《ES6-Class基本语法》https://www.cnblogs.com/ChenChunChang/p/8296350.html

优秀的个人博客,低调大师

Java 基础 之 do while 循环实例

http://www.verejava.com/?id=16992628432522 /** 连续录入学生姓名,输入“q”则系统退出 */ import java.util.Scanner; public class DoWhile2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.println("请连续录入学生姓名, 直到输入q退出系统"); String name = ""; do { name = in.nextLine(); } while (!name.equals("q")); System.out.println("退出系统"); } } /* while循环和do-while循环的区别? */ http://www.verejava.com/?id=16992628432522

优秀的个人博客,低调大师

Java 基础 之 关系运算符

http://www.verejava.com/?id=16992594990214 public class Operation3 { public static void main(String[] args) { //关系运算符 只有两种结果:真 true, 假 false /* 包括: 大于 >, 大于等于 >= 小于 <, 小于等于 <= 等于==, 不等于 != */ System.out.println(1>2); System.out.println(1>=1); System.out.println(1<2); System.out.println(1<=2); System.out.println(1==1); System.out.println(1!=2); System.out.println(1==1.0); System.out.println('A'==65); //true 不能等于1 ,false 不能等于 0; //System.out.println(true==1); } } http://www.verejava.com/?id=16992594990214

优秀的个人博客,低调大师

Java 基础 之 算数运算符

http://www.verejava.com/?id=16992579787012 /* 1. 算数运算 */ public class Operation { public static void main(String[] args) { //1. 算数运算 加+,减-,乘*,整除/,取模%,自减--,自增++ int a=1; int b=2; int c=3; int d=4; System.out.println(a+b); System.out.println(a-b); System.out.println(a*b); System.out.println(b/a); System.out.println(a/b); //取模就是整除后余下的数 System.out.println(c%b); System.out.println(d%b); System.out.println(4%-3); System.out.println(-4%-3); System.out.println(-4%3); System.out.println(d++); System.out.println(++d); System.out.println(d--); System.out.println(--d); /* 注意: 1. 整除的时候会自动切除小数部分 2. 取模的时候结果值的正负取决于分子的符号 3. 对于自增++在变量后面时是先输出,再加1, 如果++放在变量前面则是先加+再输出计算。 4. 对于自减在变量后面时是先输出再减1,如果放在变量后面是先减1再输出计算 */ } } http://www.verejava.com/?id=16992579787012

优秀的个人博客,低调大师

JavaScript 基础 --- (数据类型/循环/条件)

一、js 输出1.window.alert() 警告框 2.document.write() 写到 HTML 文档中 3.innerHTML 写到 HTML 元素 4.console.log() 写到浏览器的控制台 二、js 语句与注释 1. ; (var a = 1;) ;表示语句的结尾 2. 语句标识符:var / if / for 等 3.注释:单行 // 多行 /* */三、数据类型 1.未定义 (undefined) var x; //x 为 undefined(表示变量不含有值) 2.数字 (Number) var y = 5; //y 为数字 3.字符串 (String) var z = "John" // z为字符串 4.布尔 (Boolean) var a = true; //a 为 布尔类型 5.对象 (Object) var person ={firstname:"John",lastname:"Doe",id:5566}; 取值:console.log(person.firstname) console.log(person["firstname"]) 6.数组 (Array) 6-1. var cars = new Array(); //此时 cars 数组的长度为 0 cars[0] = "Saab"; //此时 cars 数组的长度为 1 ,js 数组是动态变化的,即第一个数赋值为 Saab car[1] = "Volvo"; //即第二个数赋值为 Volvo car[1] = "BMW"; //即第三个数赋值为 BMW 6-2. var cars = new Array("Saab","Volvo","bmw") ; 6-3. var cars = ["Saab"."Volvo","BMW"]; 7.空 (Null) var b = null //b为 null(可以用来清空变量) 四、js 变量 1.变量必须以字母开头,(不推荐 $ 和 _)区分大小写 五、js 函数 函数:是由事件驱动的或者当它被调用时执行的可重复使用的代码块 定义方法:1.function a(参数){ } 声明会前置 输出: hello world 输出: hello world 2. var a = function(参数){} 匿名函数 输出:这是一个匿名函数 输出: 1 2 3 (传递参数) return 输出: 六、js 比较与逻辑运算符 1. < 小于 > 大于 2.== 等于(5=="5") === 绝对等于(值和类型均相等 5===5) != 不等于 3.&& 与 || 或 !非 七、js 条件语句 if else if 输出: switch 输出:1 八、js 循环语句 for 输出: for in(循环遍历对象) 输出: while(指定条件为 true 时循环指定的代码块) 输出: break (跳出整个循环) 输出: continue(跳出本次循环) 输出:

优秀的个人博客,低调大师

Java基础之HashTable与ConcurrentHashMap解析

HashTable和HashMap的区别 在面试的过程中,经常会被问到HashTable和HashMap的区别,下面就这些区别做一个简单的总结。 1、继承的父类不同 Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类,但二者都实现了Map接口。 2、线程安全性不同 Hashtable 中的方法是Synchronized的,而HashMap中的方法在缺省情况下是非Synchronized的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。 总结一句话:Hashtable(1.0版本)不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。 3、是否提供contains方法 HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。 4、key和value是否允许null值 Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常。 HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。 5、遍历的内部实现方式不同 Hashtable、HashMap都使用了 Iterator。但由于历史原因,Hashtable还使用了Enumeration的方式 。 6,数组初始化和扩容方式不同 HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。具体扩容时,Hashtable将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。 HashTable 由于HashTable的性能问题,在实际编程中HashTable并不是很常见,更多的是使用HashMap或ConcurrentHashMap。 简单来说,HashTable是一个线程安全的哈希表,它通过使用synchronized关键字来对方法进行加锁,从而保证了线程安全。但这也导致了在单线程环境中效率低下等问题。 HashTable存储模型 HashTable保存数据是和HashMap是相同的,使用的也是Entry对象。HashTable类继承自Dictionary类,实现了Map,Cloneable和java.io.Serializable三个接口,其UML图如下图所示。 HashTable的功能与与HashMap中的功能相同,主要有:put,get,remove和rehash等。 HashTable的主要方法的源码实现逻辑与HashMap中非常相似,有一点重大区别就是所有的操作都是通过synchronized锁保护的。也就是说,只有获得了对应的锁,才能进行后续的读写等操作。 下面就HashTable常见的方法给大家做一个简单的解析。 构造方法 HashTable的构造方法源码如下: public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { this(11, 0.75f); } 从构造函数中可以得到如下的信息:HashTable默认的初始化容量为11(与HashMap不同,HashMap是16),负载因子默认为0.75(与HashMap相同)。而正因为默认初始化容量的不同,同时也没有对容量做调整的策略,所以可以先推断出,HashTable使用的哈希函数跟HashMap是不一样的。 put put方法的主要逻辑如下: 先获取synchronized锁; put方法不允许null值,如果发现是null,则直接抛出异常; 计算key的哈希值和index; 遍历对应位置的链表,如果发现已经存在相同的hash和key,则更新value,并返回旧值; 如果不存在相同的key的Entry节点,则调用addEntry方法增加节点; addEntry方法中,如果需要则进行扩容,之后添加新节点到链表头部。 Put方法的源码如下: public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); //计算桶的位置 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; //遍历桶中的元素,判断是否存在相同的key for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //不存在相同的key,则把该key插入到桶中 addEntry(hash, key, value, index); return null; } 涉及的Entry对象的源码如下: private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; //哈希表的键值对个数达到了阈值,则进行扩容 if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //把新节点插入桶中(头插法) tab[index] = new Entry<>(hash, key, value, e); count++; } 从上面的源码可以看到,put方法一开始就会进行值的null值检测,同时,HashTable的put方法也是使用synchronized来修饰。你可以发现,在HashTable中,几乎所有的方法都使用了synchronized来保证线程安全。 get get方法的主要逻辑如下: 先获取synchronized锁; 计算key的哈希值和index; 在对应位置的链表中寻找具有相同hash和key的节点,返回节点的value; 如果遍历结束都没有找到节点,则返回null。 get函数的源码如下: public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); //通过哈希函数,计算出key对应的桶的位置 int index = (hash & 0x7FFFFFFF) % tab.length; //遍历该桶的所有元素,寻找该key for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; } 从上面的代码可以发现,get方法使用了synchronized来修饰,以保证线程的安全,并且它是通过链表的方式来处理冲突的。另外,我们还可以看见HashTable并没有像HashMap那样封装一个哈希函数,而是直接把哈希函数写在了方法中。 rehash扩容 rehash扩容方法主要逻辑如下:数组长度增加一倍(如果超过上限,则设置成上限值);更新哈希表的扩容门限值;遍历旧表中的节点,计算在新表中的index,插入到对应位置链表的头部。 rehash方法的源码如下: protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; //扩容扩为原来的两倍+1 int newCapacity = (oldCapacity << 1) + 1; //判断是否超过最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; //计算下一次rehash的阈值 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //把旧哈希表的键值对重新哈希到新哈希表中去 for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } } HashTable的rehash方法相当于HashMap的resize方法。跟HashMap那种巧妙的rehash方式相比,HashTable的rehash过程需要对每个键值对都重新计算哈希值,而比起异或和与操作,取模是一个非常耗时的操作。这也是HashTable比HashMap低效的原因之一。 remove remove方法主要逻辑如下: 先获取synchronized锁; 计算key的哈希值和index; 遍历对应位置的链表,寻找待删除节点,如果存在,用e表示待删除节点,pre表示前驱节点。如果不存在,返回null; 更新前驱节点的next,指向e的next。返回待删除节点的value值。 remove函数的源码如下: public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; } ConcurrentHashMap HashMap是我们平时开发过程中使用的比较多的集合,但它是非线程安全的,在涉及到多线程并发的情况,进行get操作有可能会引起死循环,导致CPU利用率接近100%。例如: final HashMap<String, String> map = new HashMap<String, String>(2); for (int i = 0; i < 10000; i++) { new Thread(new Runnable() { @Override public void run() { map.put(UUID.randomUUID().toString(), ""); } }).start(); } 但是解决方法也有很多,如Hashtable和Collections.synchronizedMap(hashMap),不过这两个方案基本上是对读写进行加锁操作,一个线程在读写元素,其余线程必须等待,性能可想而知。此时,可以使用ConcurrentHashMap来解决。 JDK 1.7 ConcurrentHashMap实现 和HashMap不同,ConcurrentHashMap采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构。ConcurrentHashMap最核心的两个核心静态内部类包括:Segment和HashEntry。 理解ConcurrentHashMap需要注意如下几个概念: Segment继承ReentrantLock用来充当锁的角色,每个 Segment 对象守护每个散列映射表的若干个桶; HashEntry 用来封装映射表的键 / 值对; 每个桶是由若干个 HashEntry 对象链接起来的链表。 一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组,其数据结构如下: JDK1.8 ConcurrentHashMap实现 1.8的实现已经抛弃了Segment分段锁机制,而是采用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。而HashMap在1.8版本中也对存储结构进行了优化,采用数组+链表+红黑树的方式进行数据存储,红黑树可以有效的平衡二叉树,带来插入、查找性能上的提升。 ConcurrentHashMap在1.8版本的数据存储结构如下图: 初始化 只有在第一次执行put方法时才会调用initTable()初始化Node数组,该方法的源码如下: private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; } sizeCtl默认为0,如果ConcurrentHashMap实例化时有传参数,sizeCtl会是一个2的幂次方的值。所以执行第一次put操作的线程会执行Unsafe.compareAndSwapInt方法修改sizeCtl为-1,有且只有一个线程能够修改成功,其它线程通过Thread.yield()让出CPU时间片等待table初始化完成。 关于具体的的一些put、get、table扩容等操作,大家可以自行搜索相关的资料。

优秀的个人博客,低调大师

Java并发编程基础-线程间通信

章节目录 volatile 与 synchronized 关键字 等待/通知机制 等待/通知经典范式 管道输入/输出流 Thread.join() 的 使用 1. volatile 与 synchronized 关键字 线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一行一行的执行,直到终止。如果每个运行中的线程,仅仅是孤立的运行,那么没有价值,或者说价值很少,如果多个线程能够 相互配合 完成工作,这将带来巨大的价值。 1.1 Java 线程操作的共享变量是对共享内存变量的一份拷贝 Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个共享变量的一份拷贝 (虽然对象以及成员变量分配的内存是在共享内存中,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是 加速程序的执行)。这是现代多核处理器的一个显著特性, 所以在程序执行过程中,(未同步的程序代码块),一个线程看到的变量并不一定是最新的。 1.2 volatile 关键字-线程间通信 关键字volatile可以用来修饰字段(成员变量),就是告知任何对该变量的访问均 需要从共享内存中获取,而对它的改变必须同步刷新到共享内存, 它能保证虽有线程对共享变量的可见性。 举个例子,定义一个程序是否运行的成员变量,boolean on = true; 那么另一个 线程可能对它执行关闭动作(on = false),这涉及多个线程对变量的访问,因此 需要将其定义为 volatile boolean on = true,这样其他线程对他进行改变时,可 以让所有线程感知到变化,因为所有对共享变量的访问(load)和修改(store)都需 要以共享内存为准。但是过多的使用volatile是不必要的,因为它会降低程序执行的效率。 1.3 synchronized 关键字-线程间通信 关键字 synchronized 可以修饰方法 或者以同步块的形来进行使用,它主要确 保多个线程在同一时刻,只能有一个线程执行同步方法或同步块,它保证了线 程对变量访问的可见性、排他性。 如下所示,类中使用了同步块和同步方法,通过使用javap 工具查看生成的class文件信息来分析synchronized关键字实现细节,示例如下: package org.seckill.Thread; public class Synchronized { public static void ls(String[] args) { synchronized (Synchronized.class) { }//静态同步方法,对Synchronized Class对象进行加锁 m(); } public static synchronized void m(){ } } 执行 javap -v Synchronized.class 输出如下所示: public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // class org/seckill/Thread/Synchronized 2: dup 3: astore_1 4: monitorenter 5: aload_1 6: monitorexit 7: goto 15 10: astore_2 11: aload_1 12: monitorexit 13: aload_2 14: athrow 15: invokestatic #3 // Method m:()V 18: return public static synchronized void m(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=0, locals=0, args_size=0 0: return 对上述汇编指令进行解读 对于同步代码块(临界区)的实现使用了monitorenter 和 monitorexit 指令。 同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED。 另种同步方式的原理是 对一个充当锁的对象的monitor 进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由syntronized 所保护的对象的监视器。 任何一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到对象的监视器才能进入到同步块或者同步方法中,那么没有获取到监视器(执行改方法)的线程将会被阻塞在同步块和同步方法的入口处,进入blocked 状态。 如下是对上述解读过程的图示: 对象、监视器、同步队列、执行线程之间的关系 2.等待/通知机制 等待通知相关方法 方法名称 描述 wait() 调用lock.wait()(lock是充当锁的对象)的线程将进入waiting状态,只有等待另外线程的通知或者线程对象.interrupted()才能返回,wait()调用后,会释放对象的锁 wait(long) 超时一段时间,这里的参数是毫秒,也就是等待n毫秒,如果没有通知就超时返回 wait(long,int) 对于超时间的更细粒度控制,可以达到纳秒级别 notify() 通知一个在锁对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁(其实是线程获取到了该对象的monitor对象的控制权) notifyAll() 通知所有等待在充当锁的对象上的线程 对等待通知机制的解释 等待通知机制,是指一个线程A调用了充当锁的对象的wait()方法进入等 waiting 状态 另一个线程B调用了对象的O的 notify() 或者 notifyAll() 方法,线程A接收到通知后从充当锁的对象上的wait()方法返回,进而执行后续操作,最近一次操作是线程从等待队列进入到同步阻塞队列。 上述两个线程通过充当锁的对象 lock 来完成交互,而lock对象上的wait()/notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方的交互工作 如下代码清单所示,创建两个线程 WaitThread & NotifyThread,前者检查flag是否为false,如果符合要求,进行后续操作,否则在lock上wait,后者在睡眠一段时间后对lock进行通知。 package org.seckill.Thread; public class WaitNotify { static boolean flag = true; static Object lock = new Object();//充当锁的对象 public static void main(String[] args) { //新建wait线程 Thread waitThread = new Thread(new WaitThread(),"waitThread"); Thread notifyThread = new Thread(new NotifyThread(),"notifyThread"); waitThread.start();//等待线程开始运行 Interrupted.SleepUnit.second(5);//主线程sleep 5s notifyThread.start(); } //wait线程 static class WaitThread implements Runnable { public void run() { synchronized (lock) { //判定flag while (flag) { try { System.out.println(Thread.currentThread().getName() + "获取flag 信息" + flag); //判定为true 直接wait lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag); } } } static class NotifyThread implements Runnable { public void run() { synchronized (lock) { while (flag) { System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag+"可以运行"); lock.notify();//唤醒wait在lock上的线程,此时wait线程只能能从waiting队列进入阻塞队列,但还没有开始重新进行monitorenter的动作 // 因为锁没有释放 flag = false; Interrupted.SleepUnit.second(5); } } synchronized (lock){//有可能获取到lock对象monitor,获取到锁 System.out.println(Thread.currentThread().getName()+" hold lock again"); Interrupted.SleepUnit.second(5); } } } } 运行结果如下所示: 运行结果 对如上程序运行流程的解释如下所示: 上图中"hold lock again 与 最后一行输出"的位置可能互换,上述例子说明调用wait()、notify()、notifyAll需要注意的细节 使用wait()、notify() 和 notifyAll() 时需要在同步代码块或同步方法中使用,且需要先对调用的锁对象进行加锁(获取充当锁的对象的monitor对象) 调用wait() 方法后,线程状态由running 变为 waiting,并将当前线程放置到等待队列中 notify()、notifyAll() 方法调用后,等待线程依旧不会从wait()方法返回,需要调用notify()、notifyAll()的线程释放锁之后,等待线程才有机会从wait()方法返回 notify() 方法将waiting队列中的一个等待线程从waiting队列 移动到同步队列中,而notifyAll() 则是将等待队列中所有的线程全部移动到同步队列,被移动的线程状态由waiting status change to blocked状态 从wait() 方法返回的前提是获得了调用对象的锁等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改 3.等待/通知经典范式 等待/通知经典范式 该范式分为两部分,分别针对等待方(消费方)、和通知方(生产方)等待方遵循如下原则: 获取对象的锁 如果条件不满足,则调用对象的wait() 方法,被通知后仍要检查条件 条件满足则执行对应的逻辑 对应伪代码 syntronized (lock) { while( !条件满足 ){ lock.wait(); } //对应的处理逻辑 } 通知方遵循如下原则: 获取对象锁 改变条件 通知所有等待在锁对象的线程 syntronized(lock) { //1.执行逻辑 //2.更新条件 lock.notify(); } 4.管道输入输出流 管道输入 / 输出流和普通的文件输入/输出流 或者网络输入/输出流的不同之处在于它主要用于线程之间的数据传输,而传输的媒介为内存。 管道输入 / 输出流主要包括如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader 、PipedWriter 前两种面向字节,后两种面向字符 对于Piped类型的流,必须先进行绑定,也就是调用connect()方法,如果没有输入/输出流绑定起来,对于该流的访问将抛出异常。 5.Thread.join() 的 使用 如果使用了一个线程A执行了thread.join ,其含义是线程A等待thread线程终止之后才从thread.join()返回。 如下笔试题: 有A、B、C、D四个线程,在main线程中运行,要求 执行顺序是A->B->C->D->mian 变种->main等待A、B、C、D四个线程顺序执行,且进行sum,之后main线程打印sum解法1-join() 其实就是插队 package org.seckill.Thread; public class InOrderThread { static int num = 0; public static void main(String[] args) throws InterruptedException { Thread previous = null; for (int i = 0; i < 4; i++) { char threadName = (char) (i + 65); Thread thread = new Thread(new RunnerThread(previous), String.valueOf(threadName)); previous = thread; thread.start(); } previous.join(); System.out.println("total num=" + num); System.out.println(Thread.currentThread().getName() + "terminal"); } static class RunnerThread implements Runnable { Thread previous;//持有前一个线程引用 public RunnerThread(Thread previous) { this.previous = previous; } public void run() { if (this.previous == null) { // num += 25; System.out.println(Thread.currentThread().getName() + " terminate "); } else { try { previous.join(); } catch (InterruptedException e) { e.printStackTrace(); } // num += 25; System.out.println(Thread.currentThread().getName() + " terminate "); } } } } 解法2-wait/notify package org.seckill.Thread; //wait/notify public class InOrderThread2 { // static int state = 0;//运行标志 // static Object lock = new Object(); public static void main(String[] args) { // RunnerThread runnerThreadA = new RunnerThread(); // RunnerThread runnerThreadB = new RunnerThread(); // RunnerThread runnerThreadC = new RunnerThread(); // RunnerThread runnerThreadD = new RunnerThread(); // Thread threadA = new Thread(runnerThreadA, "A"); // Thread threadB = new Thread(runnerThreadB, "B"); // Thread threadC = new Thread(runnerThreadC, "C"); // Thread threadD = new Thread(runnerThreadD, "D"); RunnerThread runnerThread = new RunnerThread(); Thread threadA = new Thread(runnerThread, "A"); Thread threadB = new Thread(runnerThread, "B"); Thread threadC = new Thread(runnerThread, "C"); Thread threadD = new Thread(runnerThread, "D"); threadD.start(); threadA.start(); threadB.start(); threadC.start(); } static class RunnerThread implements Runnable { // private boolean flag = true; static int state = 0;//运行标志 static Object lock = new Object(); public void run() { String threadName = Thread.currentThread().getName(); // while (flag) { // synchronized (lock) { // if (state % 4 == threadName.charAt(0) - 65) { // state++; // flag = false; // System.out.println(threadName + " run over"); // } // } // } synchronized (lock) { while (state % 4 != threadName.charAt(0) - 65) { try { lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } state++; System.out.println(threadName+" run over "); lock.notifyAll(); } } } } 等待/通知范式做线程同步 是非常方便的。 解法3-循环获取锁 package org.seckill.Thread; //wait/notify public class InOrderThread2 { static int state = 0;//运行标志 static Object lock = new Object(); public static void main(String[] args) { RunnerThread runnerThreadA = new RunnerThread(); RunnerThread runnerThreadB = new RunnerThread(); RunnerThread runnerThreadC = new RunnerThread(); RunnerThread runnerThreadD = new RunnerThread(); Thread threadA = new Thread(runnerThreadA, "A"); Thread threadB = new Thread(runnerThreadB, "B"); Thread threadC = new Thread(runnerThreadC, "C"); Thread threadD = new Thread(runnerThreadD, "D"); // RunnerThread runnerThread = new RunnerThread(); // Thread threadA = new Thread(runnerThread, "A"); // Thread threadB = new Thread(runnerThread, "B"); // Thread threadC = new Thread(runnerThread, "C"); // Thread threadD = new Thread(runnerThread, "D"); threadD.start(); threadA.start(); threadB.start(); threadC.start(); } static class RunnerThread implements Runnable { private boolean flag = true;//每个线程的私有变量 // static int state = 0;//运行标志 // static Object lock = new Object(); public void run() { String threadName = Thread.currentThread().getName(); while (flag) {//主动循环加锁 synchronized (lock) { if (state % 4 == threadName.charAt(0) - 65) { state++; flag = false; System.out.println(threadName + " run over"); } } } // // synchronized (lock) { // while (state % 4 != threadName.charAt(0) - 65) { // try { // lock.wait(); // }catch (InterruptedException e){ // e.printStackTrace(); // } // } // state++; // System.out.println(threadName+" run over "); // lock.notifyAll(); // } } } } 开销是极大的、难以确保及时性 解法4-CountDownLatch package org.seckill.Thread; import java.util.concurrent.CountDownLatch; public class InOrderThread3 { // static int state = 0;//运行标志 // static Object lock = new Object(); public static void main(String[] args) throws InterruptedException{ CountDownLatch countDownLatchA = new CountDownLatch(1); CountDownLatch countDownLatchB = new CountDownLatch(1); CountDownLatch countDownLatchC = new CountDownLatch(1); CountDownLatch countDownLatchD = new CountDownLatch(1); RunnerThread runnerThreadA = new RunnerThread(countDownLatchA); RunnerThread runnerThreadB = new RunnerThread(countDownLatchB); RunnerThread runnerThreadC = new RunnerThread(countDownLatchC); RunnerThread runnerThreadD = new RunnerThread(countDownLatchD); Thread threadA = new Thread(runnerThreadA, "A"); Thread threadB = new Thread(runnerThreadB, "B"); Thread threadC = new Thread(runnerThreadC, "C"); Thread threadD = new Thread(runnerThreadD, "D"); // RunnerThread runnerThread = new RunnerThread(); // Thread threadA = new Thread(runnerThread, "A"); // Thread threadB = new Thread(runnerThread, "B"); // Thread threadC = new Thread(runnerThread, "C"); // Thread threadD = new Thread(runnerThread, "D"); threadA.start(); countDownLatchA.await();//主线程阻塞,待countDownLatch 减为0即可继续向下运行 threadB.start(); countDownLatchB.await(); threadC.start(); countDownLatchC.await(); threadD.start(); countDownLatchD.await(); System.out.println(Thread.currentThread().getName()+" run over "); } static class RunnerThread implements Runnable { // private boolean flag = true; // static int state = 0;//运行标志 // static Object lock = new Object(); CountDownLatch countDownLatch; RunnerThread(CountDownLatch countDownLatch){ this.countDownLatch = countDownLatch; } public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName+" run over"); countDownLatch.countDown(); // while (flag) { // synchronized (lock) { // if (state % 4 == threadName.charAt(0) - 65) { // state++; // flag = false; // System.out.println(threadName + " run over"); // } // } // } // // synchronized (lock) { // while (state % 4 != threadName.charAt(0) - 65) { // try { // lock.wait(); // }catch (InterruptedException e){ // e.printStackTrace(); // } // } // state++; // System.out.println(threadName+" run over "); // lock.notifyAll(); // } } } } countDownLatch 的使用场景 :比如系统完全开启需要等待系统软件全部运行之后才能开启。最终的结果一定是发生在子(部分)结果完成之后的。也可作为线程同步的一种方式Thread join() 源码 public final synchronized void join() throws InterruptedException { while (isAlive) { wait(0); } } 当被调用thread.join() 的线程(thread)终止运行时,会调用自身的notifyAll()方法,会通知所有等待该线程对象上完成运行的线程,可以看到join方法的逻辑结构与等待/通知经典范式一致,即加锁、循环、处理逻辑3个步骤。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册