对于想了解java基础知识点的读者,本文将是一篇不可错过的文章,我们将详细介绍java基础知识点思维导图,并且为您提供关于java基础基础知识点总结、JAVA基础知识、Java基础知识2-Java基本
对于想了解java基础知识点的读者,本文将是一篇不可错过的文章,我们将详细介绍java基础知识点思维导图,并且为您提供关于java基础基础知识点总结、JAVA基础知识、Java基础知识2-Java基本语法、Java基础知识_多线程必要知识点的有价值信息。
本文目录一览:java基础知识点(java基础知识点思维导图)
1、关键字和保留字
关键字(Keywords)
break else new var case finally return void
catch for switch while continue function this with
default if throwdelete in try do instanceoftypeof
保留字(ReservedWords)
abstractenumintshort booleanexport interface static
byteextends long super char final native synchronized
classfloat package throws constgotoprivate transient
debuggerimplements protected volatile double import public
2、基本语法标识符是指JavaScript中定义的符号,例如,变量名,函数名,数组名等。标识符可以由任意顺序的大小写字母、数字、下划线(_)和美元符号($)组成,但标识符不能以数字开头,不能是JavaScript中的保留字或关键字。
合法的标识符举例:indentifier、username、user_name、_userName、$username
非法的标识符举例:int、98.3、Hello World
username和userName是两个完全不同的符号
每条功能执行语句的最后必须用分号(;)结束,每个词之间用空格、制表符、换行符或大括号、小括号这样的分隔符隔开。
/*…*/中可以嵌套“//”注释,但不能嵌套“ /*…*/”。 、/**..文档注释.*/
3、基本数据类型及常量
A、整型常量(10进制\8进制\16进制)
十六进制以0x或0X开头,例如:0x8a。
八进制必须以0开头,例如:0123。
十进制的第一位不能是0(数字0除外),例如:123。
B、实型常量
12.32、193.98、 5E7、4e5等。
特殊数值:NaN、Infinity(除数为零),所对应的判断函数isNaN()、isFinite()
true和false。
“abook of JavaScript”、‘a’、 “a”、“”。
字符串中的特殊字符,需要以反斜杠(\)后跟一个普通字符来表示,例如:\r、\n、\t、\b、\''、\ "、\\xxx
(1)算术运算符
当左边操作数大于右边操作数时返回true,否则返回false。
当左边操作数小于右边操作数时返回true,否则返回false。
当左边操作数大于等于右边操作数时返回true,否则返回false。
当左边操作数小于等于右边操作数时返回true,否则返回false。
当左边操作数等于右边操作数时返回true,否则返回false。
当左边操作数不等于右边操作数时返回true,否则返回false。
(4)逻辑运算符逻辑与,当左右两边操作数都为true时,返回值为true,否则返回false。
逻辑或,当左右两边操作数都为false时,返回其中第一个不为false的值或者最后一个值。
逻辑非,当操作数为true时,返回值为false,否则返回true。
注意:
在逻辑运算中,0、""、false、null、undefined、NaN均表示false。
(5)位运算符
(1)if语句
if(条件语句1)
{
执行语句块1
}
elseif(条件语句2)
{
执行语句块2
}
...
elseif(条件语句n)
{
执行语句块n
}
else
{
执行语句块n+1
}
(2)switch语句
switch(表达式)
{
case 取值1:
语句块1
break;
…
case 取值n:
语句块n
break;
default:
语句块n+1
break;
}
(3)while语句
while(条件表达式语句)
{
执行语句块
}
(4)do while语句
do
{
执行语句块
}while(条件表达式语句);
(5)for语句
for(初始化表达式;循环条件表达式;循环后的操作表达式)
{
执行语句块
}
(6)for in语句: forin是对对象进行迭代,迭代出对象中所有的属性和方法
for(变量 in 集合或对象)
{
执行语句块
}
例子:
var as={id:5,name:''test''};
for(var x in as)
{
output += x+"="+as[x];
}
alert(output);
(7)break语句:
break语句 :
st:while(true)
{
while(true)
{
break st;
}
}
(8)continue语句
var output = "";
for(var x=1; x<10; x++)
{
if(x%2 == 0)
continue;
output=output + " x=" + x;
}
alert(output);
6、javascript中的系统函数(Global类)
返回对一个URI字符串编码后的结果。
将一个已编码的URI字符串解码成最初始的字符串并返回。
注意:
encodeURI/decodeURI
该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII标点符号进行编码: - _ . ! ~ * '' ( ) 。
该方法的目的是对URI进行完整的编码,因此对以下在URI中具有特殊含义的ASCII标点符号,encodeURI()函数是不会进行转义的:;/?:@&=+$,#
encodeURIComponent/decodeURIComponent
该方法不会对ASCII字母和数字进行编码,也不会对这些ASCII标点符号进行编码: -_ . ! ~ * '' ( ) 。
其他字符(比如:;/?:@&=+$,#这些用于分隔URI组件的标点符号),都是由一个或多个十六进制的转义序列替换的。
将一个字符串按指定的进制转换成一个整数,语法格式为:parseInt(numString,[radix])。如果没有指定第二个参数,则前缀为‘0x’ 的字符串被视为十六进制,前缀为 ‘0’ 的字符串被视为八进制,所有其他字符串都被视为是十进制。
将一个字符串转换成对应的小数。
用于检测parseInt和parseFloat方法的返回值是否为NaN。
返回对一个字符串进行编码后的结果字符串。所有空格、标点、重音符号以及任何其他非ASCII字符都用%xx编码替换,其中xx等于表示该字符的Unicode编码的十六进制数,字符值大于255的字符以%uxxxx格式存储。
将一个用escape方法编码的结果字符串解码成原始字符串并返回。
将其中的参数字符串作为一个JavaScript表达式执行。
<完>java基础基础知识点总结
java后端5年经验和技术总结
1.引言
毕业已经5年有余,这5年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这5年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火队员的苦恼。遂决定梳理一下自己所学的东西,为大家分享一下。
经过一年意识到以前也有很多认识误区,比如:
偏爱收集,经常收集各种资料视频塞满一个个硬盘,然后心满意足的看着容量不行动。
不重基础,总觉得很多基础东西不需要再看了,其实不懂的地方很多,计算机程序方面任何一个结果都必有原因,不要只会用不知道原理,那是加工厂出来的。现在ide查看代码那么方便,ctrl+点击就进入了JDK查看实现细节。
好高骛远,在计算机基础不牢固的情况下,总想着要做架构,弄分布式,搞大数据之类。
不重视性能,只求能实现功能,sql查询是不是可以优化,是否有算法妙用,大对象是否要清除。
不重视扩展性,模块之间紧密耦合,常用方法不提取成工具类,调用关系混乱等问题。
……
PS:如果你也在学习或者工作过程中遇到诸多问题,可以查看我的公告栏,随问随答,还有我这段时间整理的一些Java学习手册,面试题,开发工具,PDF文档书籍教程,需要的话都可以免费分享。
本文重点不在这些,故只列举了一小部分,下面进入正题。
2.语法基础
2.1 Java类初始化顺序
这是所有情况的类初始化顺序,如果实际类中没有定义则跳过:父类静态变量——父类静态代码块——子类静态代码块——父类非静态变量——父类非静态代码块——父类构造函数——子类非静态变量——子类非静态代码块——子类构造函数
2.2 值传递和引用传递
可能很多人对此不屑一顾,心想老子都工作一年了,对这些还不熟悉吗?但实际情况并非这样,JDK中东西全部熟悉了吗?以一个最简单的例子开始,你觉得下图中代码执行完之后fatherList中的元素是什么?
这是一个最基础的值传递和引用传递的例子,你觉得好简单,已经想跃跃欲试的挑战了,那么请看下面的,StringBuffer很好理解,但是当你执行一遍之后发现是不是和预想中的输出不一样呢?String不是引用类型吗,怎么会这样呢?如果你无法理解,那么请看下String的实现源码,了解下其在内存中分配的实现原理。
2.3 集合的使用
这部分几乎每个人都会用到,而且大家还都不陌生。下图来源于互联网,供大家复习一下。但是利用集合的特性进行巧妙的组合运用能解决优化很多复杂问题。Set不可重复性,List的顺序性,Map的键值对,SortSet/SortMap的有序性,我在工作中有很多复杂的业务都巧妙的使用了这些,涉及到公司保密信息,我就不贴出代码了。工作越久越发现这些和越巧妙。
2.3 异常处理
1.看着try、catch、finally非常容易,如果和事务传播结合在一起,就会变得极其复杂。
2.finally不一定必须执行,return在catch/finally中处理情况(建议亲自操刀试一下)。
3.catch中可以继续抛自定义异常(并把异常一步步传递到控制层,利用切面抓取封装异常,返回给调用者)。
2.4 面向对象思想
一提起面向对象,大家都知道抽象、封装、继承、和多态。但是实际工作经验中又知道多少呢,对于项目中如何巧用估计更不要提了。
共性的机会每个都需要用的建立基类,如每个控制层方法可能要通过security获取一个登录用户id,用于根据不同的用户操作不同的数据,可以抽象出一个应用层基类,实现获取id的protect方法。同理DAO层可以利用泛型提取出一个包含增删改查的基类。
多态的Override:基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象,如果指向子类的实例对象,其调用的方法应该是正在运行的那个对象的方法。在策略模式中使用很普遍。
提到面向对象,就不可避免的要说设计模式,在工作中,一个技术大牛写的一个类似策略模式(更复杂一点),十分巧妙的解决了各种业务同一个方法,并且实现了订单、工单、业务的解耦,看得我是非常佩服。我想很多面试中都会问道单例模式吧,还没有理解的建议去看一看。
3.多线程
3.1 线程安全
这个是老生常谈的问题了,但是确实是问题和bug高发区。线程同步问题不需要单独写了,想必大家都清楚,不太熟悉的建议百度一下。
3.1.1 线程安全问题
1.代码中如果有同步操作,共享变量要特别注意(这个一般都能意识到)
2多个操作能修改数据表中同一条数据的。(这个容易被忽略,业务A可能操作表a,业务B也可以操作表a,业务A、B即使在不同的模块和方法中,也会引起线程安全问题。例如如果一个人访问业务A接口,另一个人访问业务B接口,在web中每个业务请求都是会有单独的一个线程进行处理的,就会出现线程安全问题)。
3.不安全的类型使用,例如StringBuffer、StringBuild,HashTable、HashMap等。在工作中我就遇到过有人在for循环进行list的remove,虽然编译器不报错,程序可以运行,但是结果却可想而知。
4.Spring的bean默认是单例的,如果有类变量就要特别小心了(一般情况下是没人在控制层、业务层、DAO层等用类变量的,用的话建议是final类型,例如日志log,gson等)。
5.多个系统共享数据库情况,这个其实和分布式系统类似
用户重复提交问题(即使代码中从数据库读取是否存在进行限制不能解决问题)
3.1.2 线程安全解决
在需要同步的地方采用安全的类型。
JDK锁机制,lock、tryLock,synchronized,wait、notify、notifyAll等
Concurrent并发工具包,在处理一些问题上,谁用谁知道。强烈建议查看源码!
数据表加锁。(除非某个表的访问频率极低,否则不建议使用)
涉及分布式的,采用中间件技术例如zookeeper等解决。
3.2 异步
异步使用场景不影响主线程,且响应较慢的业务。例如IO操作,第三方服务(短信验证码、app推送、云存储上传等)。
如果异步任务很多,就需要使用任务队列了,任务队列可以在代码级别实现,也可以利用redis(优势太明显了)。
3.3 多线程通信
这方面文章非常多,这里不在详述。
1.共享变量方式(共享文件、全局变量,信号量机制等)
2.消息队列方式
3. 忙等,锁机制
3.4多线程实现
1.集成Thread类,重写(这里的重写指的是override)run方法,调用start方法执行。
2.实现Runable接口,实现run方法,以Runable实例创建thread对象。
3.实现Callable接口,实现call方法,FutureTask包装callable接口,FutureTask对象创建thread对象,常用语异步操作,建议使用匿名内部类,方便阅读和使用。
额外需要说明的是:
1.理解thread的join方法;
2.不要认为volitate是线程安全的(不明白原因的建议去看jvm运行时刻内存分配策略);
3.sleep时间片结束后并不保证立马获取cpu。
4.ThreadLocal能够为每一个线程维护变量副本,常用于在多线程中用空间换时间。
4. 开源框架
4.1 Hibernate、Mybatis
相信每一个java程序员对这些都不陌生,这里不再详述。
需要说明的主要以下几点:
1.hibernate一级缓存(内置session缓存),二级缓存(可装配sessionFactory缓存),二级缓存会引起并发问题。
2.hibernate延迟加载原理理解。
3.hibernate 的get、load方法,sava、persist、savaOrUpdate方法区别
4.session重建了关联关系却并没有同数据库进行同步和更新
5.hibernate session关联关系:detached对象、persistent对象
6.Spring data集成,注解方式配置属性和实体。
7.mybatis 插件。
8.分页查询(数据库)。
9.连接池技术
4.2 Spring IOC
4.1.1 Spring bean
1.bean注入 注解方式方便易读,引用第三方(数据库连接,数据库连接池,JedisPool等)采用配置文件方式。
2. bean作用域:Singleton,prototype,request,session,global session
3.bean生命周期:如下图所示(图片来源于互联网.........
JAVA基础知识
JVM JAVA虚拟机 源代码通过编译器编译成字节码,字节码通过JVM与操作系统联系
JRE JVM、库函数、JAVA运行必备的函数
JDK JRE、编译器、调试器
变量的本质:可操作的存储空间
静态变量 用static修饰,从属与类
byte 1字节 -128-127 1字节占8位 一个位置表示符号 -27 ~27
short 2字节 -32768-32767
int 4字节 -21亿-正21亿
long 8字节 -264-264
八进制以0开头 如:015(13)
十六进制以0X或者0x开头 如:0X15(21)
二进制以0B或者0b开头 如:0B1101(13)
float 4字节 赋值时需要后缀加f
double 8字节
浮点数不精确,不要用于比较,使用math包下的BigInteger和BigDecimal
//加法
BigDecimal result1 = num1.add(num2);
//减法
BigDecimal result2 = num1.subtract(num2);
//乘法
BigDecimal result3 = num1.multiply(num2);
//绝对值
BigDecimal result4 = num3.abs();
//除法
BigDecimal result5 = num2.divide(num1,20,BigDecimal.ROUND_HALF_UP);
二元运算符的运算规则:
整数运算:1、如果两个数有一个为long,结果也为long 2、如果没有long,结果为int
浮点运算:1、如果两个数有一个为double,结果也为double 2、只有两个都为float,结果才是float
取余运算:结果与左边数字正负相同 如:7%-3=1 -7%3=-1
使用Scanner获取键盘输入
Scanner scanner = new Scanner(System.in);
String abc = scanner.nextLine();
while循环
先判断,不符合判断条件直接跳出,符合判断条件则进入循环体,执行完循环体后再次判断是否符合条件
while(布尔表达式){
循环体;
}
do-whlie循环
先执行一次循环体,再判断是否符合判断条件,符合判断条件则进入循环体,执行完循环体后再次判断是否符合条件
do{
循环体;
}while(布尔表达式);
break:直接结束整个循环,跳出循环体。
continue:结束本次循环,继续循环体。
return:结束所属的方法。
标签指的是后面跟一个冒号的标识符,例如“lable:",当在内循环中需要跳到外循环时使用,类似于goto关键字
public class ContinueTest {
/**
* @param args
*/
public static void main(String[] args) {
outer:for(int i=101;i<200;i++){
for(int j=2;j<i/2;j++){
if(i%j==0){
continue outer;
}
}
System.out.println(i);
}
}
}
带标号的break浯句也有两个特点:一是一定会有一个带相同标号的程序块和带标号的break语句一起出现;二是这样的break语句使程序可以从多重循环的内层循环跳出来,也可以从外层循环或其他语句处跳出来,转到语句块的后面执行
重载(overload):方法名相同,参数类型不同、参数数量不同、参数顺序不同
重构(override) :
递归算法的本质即“自己调用自己”,一个递归方法直接或间接的调用自己,缺点:占用大量的堆栈,内存消耗大,速度慢
/**
* Title: 阶乘的实现
* Description:
* 递归解法
* 非递归解法
* @author rico
*/
public class Factorial {
/**
* @description 阶乘的递归实现
* @author
* @created 2017年5月10日 下午8:45:48
* @param n
* @return
*/
public static long f(int n){
if(n == 1) // 递归终止条件
return 1; // 简单情景
return n*f(n-1); // 相同重复逻辑,缩小问题的规模
}
--------------------------------我是分割线-------------------------------------
/**
* @description 阶乘的非递归实现
* @author
* @created 2017年5月10日 下午8:46:43
* @param n
* @return
*/
public static long f_loop(int n) {
long result = n;
while (n > 1) {
n--;
result = result * n;
}
return result;
}
}
Java基础知识2-Java基本语法
数据类型
1.Java程序的基本组成
- 关键字:被Java语言赋予特定含义的单词,不能作标识符,如private。
- 标识符:由数字、字母、$和_组成的字符串,用于引用变量。且首字母不能是数字。
- 变量:程序执行过程中,在某个范围内其值发生改变的量。
- 常量:在执行过程中其值不发生改变,分为字面值常量和自定义常量,如3.14。
- 表达式:Java中使用的是中缀表达式。如 double a +(b-c)/2。
2.基本数据类型:
类型 |
占用存储空间 |
值域 |
byte |
1字节 |
-128~127 |
short |
2字节 |
-2^15~2^15-1 |
int |
4字节 |
-2^31~2^31-1 |
long |
8字节 |
-2^63~2^63-1 |
float |
4字节 |
-3.403E~3.403E38 |
double |
8字节 |
-1.798E308~1.798E308 |
类型转换:
- 默认转换:byte,short,char—>int—>long—>float—>double.其中byte,short,char相互之间不能转换,他们参与运算首先转换成int类型。若强行将计算的赋值表达式与变量运算,则会发生编译失败。如
- 强制转换:目标类型 变量名 = (目标类型)(被转换的数据);转换成二进制进行考虑。比如要打印 byte a = 130,可以利用强制转换来实现。
3.运算符
运算符优先级表
(1)算术运算符(+,-,*,/,%,++,--)
A:+的用法
a:加法
b:正号
c:字符串连接符 (对+进行了重载)
B:/和%的区别
a: / 整数之间的相除,结果是整数。
向0取整
如果想得到小数,*1.0即可。
b: %
判断一个数是否是奇数
a % b = a - (a / b) *b
取余运算是一个很耗时的操作,在性能比较关键的时候最好不要使用
D:++和--的用法
a:他们的作用是自增或者自减
b:使用
**单独使用
放在操作数据的前面和后面效果一样。
a++或者++a效果一样。
**参与操作使用
放在操作数的前面:先自增或者自减,再参与操作
放在操作数的后面:先参与操作,再自增或者自减
c:底层原理:
a = a++;
int temp = a;
a++;
a = temp;
(2)赋值运算符(=,+=,-=,*=,/=,%=等)
A:=叫做赋值运算符,也是最基本的赋值运算符
int x = 10; 把10赋值给int类型的变量x。
a = b = 10;
赋值运算符是从右向左结合的。
赋值表达式有值的,值等于=右边表达式的值。
B:扩展的赋值运算符的特点
隐含了自动强制转换。
面试题:
short s = 1;
//s = s + 1;
short s = 1;
s += 1;
请问上面的代码哪个有问题?
(3)比较运算符( ==,!=,>,>=,<,<=,instanceof)
A:无论运算符两端简单还是复杂最终结果是boolean类型。
B:千万不要把==写成了=
(4)逻辑运算符(&,|,^,!,&&,||)
A:逻辑运算符用于连接boolean类型的式子
B:结论
&: 有false则false
|: 有true则true
^: 相同则false,不同则true。
情侣关系。
!: 非true则false,非false则true
&&: 结论和&是一样的,只不过有短路效果。左边是false,右边不执行。
||: 结论和|是一样的,只不过有短路效果。左边是true,右边不执行。
推荐使用 &&,||
(5)位运算符 (&, |, ^, ~, <<, >>, >>>)
A:结论:
&:同时为1,才为1,否则为0
|:同时为0,才为0,否则为1
^: 相同为0,不同为1;
a ^ b = b ^a;
a ^ 0 = a;
a ^ a = 0;
~: ~0为1,~1为0;
<<: 移除的高位丢弃,低位补0;
左移n个单位,相当于乘以 2 ^ n;
>>: 移除的低位丢弃,如果符号位是0,则高位补0,如果符号位是1,则高位补1;
右移n个单位,相当于除以2 ^ n;
>>>: 移除的低位丢弃,高位补0;
注意事项:如何移动的单位a超过了字长m, 则实际移动
a mod m (数学中的取余运算)
B:面试题
a: 判断一个数是否是奇数 a & 1 == 1;
b: 对2 ^ n 取余。 a & (2^n - 1);
c: 如何判断一个数是否是2^n a & (a-1) == 0
d: 两个整数之间的交换;
e: 有一堆数,都是成对出现的,只有一个单独的,找出这个单独的数
f: 用最有效率的方式求2 * 8的值 2 << 3;
(6)三元运算符
A:格式
比较表达式 ? 表达式1 : 表达式2;
B:执行流程:
首先计算比较表达式的值,看是true还是false。
如果是true,表达式1就是结果。
如果是false,表达式2就是结果。
C:案例:
a:比较两个数据是否相等
b:获取两个数据中的最大值
c:获取三个数据中的最大值
int max = (a > b ? a : b) > c ? (a > b ? a : b) : c;
int max = a > b ? (a > c ? a : c) : (b > c ? b : c);
语句
1.声明语句
创建某种类型的变量并用标识符为其命名,Java是一种强类型的语言,编译器会检查类型的一致性。所以在首次使用变量时一定要声明。如 int a,b;
2.赋值语句
如 c = 5,是将5这一int型常量赋值给变量c,赋值语句的左侧必须是单个变量,右侧可以是能够得到相应类型值的任意表达式。
3.条件语句
根据制定的条件执行分支代码块之一,有if和swich两种语句结构
if语句
if(<boolean expression>)
{
<block statements1>
}else
{
<block statements2>
}
switch语句
switch(<boolean expression>)
{
case value1:
<block statements1>
break; //可以结束switch语句
case value2:
<block statements2>
break;
…
default: //当所有情况都不匹配的时候,就执行该处的内容
<block statements>
break;
}
- if的关系表达式的结果必须是boolean类型
- switch的关系表达式的取值可以是:byte,short,int,char
- JDK5以后可以是枚举,JDK7以后可以是String
- case后面只能是常量,不能是变量。default,break均可以省略,但不建议
- break 与 continue 语句
- break:立即从循环中退出
- continue:立即开始下一轮循环
4.循环语句
while语句:
while(<boolean expression>)
{
<block statements>
}
循环语句中的代码段称为循环体。
for语句:
以一种更紧凑的方式来表达while的循环
for(<initialize>; <boolean expression>; <increment>)
{
<block statements>
}
5.跳转控制语句
break 中断
- 使用场景:选择结构的switch语句,循环语句,其他场景无意义,会报错
- 作用:跳出单层循环(若有多层,只跳出内层),跳出多层循环,则需要标签语句的配合
continue 继续
- 使用场景:循环语句
- 作用:退出这次迭代,开始下次迭代(循环的一次)
return 返回
- 结束一个方法,一旦遇到return,方法不会再继续往后执行
数组
1.概念:
- 数组是存储同一种数据类型多个元素的集合
2.静态初始化:
- 给出值,系统在Java中的数组必须先初始化,才能使用,一般需要三步(声明数组的名字和类型,创建数组,初始化数组元素)
3.动态初始化:
- 只给长度,系统给出默认值。如 double[] a = new double[N]; //等号左侧声明数组,右侧创建了数组。此时默认初始值是0.0。
编译时将数组初始化。如int[] a = { 1, 1, 2, 5, 8};
4.二维数组:
- 二维数组就是一维数组的数组,如 double[][] a = new double[M][N];
方法
含义:
就是完成特定功能的代码块,在Java中,类似于其他语言中的函数,被称为方法。格式:
修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2...) {
方法体;
return 返回值;
}
修饰符:目前就用 public static。后面再详细讲解其他修饰符
返回值类型:就是功能结果的数据类型
方法名:就是起了一个名字,方便我们调用该方法。
参数类型:就是参数的数据类型
参数名:就是变量名
参数分类:
实参:调用方法时,传递的参数
形参:定义方式时,定义的参数。
方法体语句:就是完成功能的代码块
return:结束方法,并把计算结果返回给调用者。
返回值:就是功能的结果,由return带给调用者。
3.性质:
方法的参数按值传递:
- 在方法中参数变量的使用方法和局部变量相同,唯一不同的是参数变量的初始值是由调用方提供的。方法处理的是参数的值,而非参数本身。这种方式产生的结果是在静态方法中改变一个参数变量的值对调用者没有影响。
方法名可以被重载:
- 所谓重载,指的是在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型,参数顺序不同。
- 方法重载与返回值类型无关,只看方法名和参数列表(调用时虚拟机通过参数列表的不同来区分同名的方法)
方法只能返回一个值,但是可以包含多个返回语句:
- 一个Java方法只能返回一个值,它的类型是方法签名中声明的类型,如double,int等等。任何静态方法每次都只会返回一个值,即被执行的第一条返回语句的参数。
方法可以产生副作用:
- 方法的返回值可以是void,这表示该方法没有返回值。方法的组后一条语句执行完毕后控制权将会返回给调用方。我们称void类型的静态方法会产生副作用(接受输入,产生输出,修改数组或者改变系统状态)。
Java基础知识_多线程必要知识点
一、使用多线程遇到的问题
1.1 线程安全问题
在前面的文章中,多线程主要是为了提高我们应用程序的使用率。但同时,这会给我们带来很多安全问题。
如果我们在单线程中以顺序(串行->独占)的方式执行代码是没有任何问题的。但是到了多线程环境下(并行),如何没有设计和控制得好,就会给我们带来很多意想不到得状况,也就是线程安全问题。
因为在多线程得环境下,线程是交替执行得,一般他们会使用多个线程执行相同得代码,如果在此相同得代码里面有共享得变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题。
简单举个例子:
下面的程序在单线程中跑起来,是没有问题的
1 public class UnsafeCountingServlet extends GenericServlet implements Servlet {
2 private long count = 0;
3
4 public long getCount() {
5 return count;
6 }
7
8 public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
9
10 ++count;
11 // To something else...
12 }
13 }
但是在多线程环境下跑起来,它的count值计算就不对了。
首先,它共享了count这个变量,其次来说++count实际上是一个组合操作(并不是原子性的)
++count实际上操作是
读取count值
将值+1
将计算结果写入count
于是多线程执行的时候很可能出现这样的情况
当线程A读取到count值是8的时候,同时线程B也进去这个方法上了,也是读取count值为8
它俩同时对值+1
将计算结果写入到count。但是写入到count的结果是9
也就是说:两个线程进来了,但是正确的结果应该是10,而它返回的是9,这是不正常的!
如果说:当多个线程访问某个类的时候,这个类始终能表现出正确的行为,那么这个类是线程安全的。
有个原则:能够使用JDK提供的线程安全机制,就使用JDK的
1.3 性能问题
使用多线程我们的目的就是为了提供应用程序的使用率,但是多线程的代码没有好好设计的话,那未必会提高效率
反而会降低效率,甚至有可能造成死锁
就比如我们的Servlet,一个Servlet对象可以处理多个请求的,Servlet显然是一个天然支持多线程的。
又以下面的例子来说吧
public class UnsafeCountingServlet extends GenericServlet implements Servlet {
private long count = 0;
public long getCount() {
return count;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
++count;
// To something else...
}
}
上面我们已经说了,这个类是线程不安全的。最简单的方式:如果我们在service方法上加上JDK为我们提供的内置锁synchronized,那么我们就可以实现线程安全了。
1 public class UnsafeCountingServlet extends GenericServlet implements Servlet {
2 private long count = 0;
3
4 public long getCount() {
5 return count;
6 }
7
8 public void synchronized service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
9
10 ++count;
11 // To something else...
12 }
13 }
虽然实现了线程安全了,但是这会带来很严重的性能问题:
每个请求都得等待上一个请求的service方法处理了以后才可以完成对应的操作
这就导致了:我们完成一个小小的功能,使用多线程的目的是想要提高效率,但现在没有把握得当,却带来了严重的性能问题
在使用多线程的时候,更严重的时候还有死锁(程序卡住就不动了)
这些都是我们接下来要学习的地方:学习使用哪种同步机制来实现线程安全,并且性能是提高了而不是降低了
二、对象的发布与溢出
书上是这样定义发布与逸出的
发布(publish)使对象能够在当前作用域之外的代码中使用
逸出(escape)当某个不应该发布的对象被发布了
常见的逸出的有下面几种方式
静态域逸出
public修饰的get方法
方法参数传递
隐式的this
静态域溢出
public修饰get方法
方法参数传递我就不再演示了,因为把对象传递过去给另外的方法,已经是逸出了。
下面来看看this逸出的例子
逸出就是本不应该发布对象的地方,把对象发布了。导致我们的数据泄漏出去了,这就造成了安全隐患
2.1 安全发布对象
上面谈到了好几种逸出的情况,我们接下来谈谈如何安全发布对象
安全发布对象有常用的几种方式
在静态域中直接初始化:public static Person = new Person()
;
静态初始化由JVM在类的初始化阶段就执行了,JVM内部存在着同步机制,致使这种方式我们可以安全发布对象
对应的引用保存到volatile或者AtomicReference引用中
保证了该对象的引用的可见性和原子性
由final修饰
该对象是不可变的,那么线程就一定是安全的,所以是安全发布
由锁来保护
发布和使用的时候都需要加锁,这样才能保证该对象不会逸出
三、解决多线程遇到的问题
从上面我们就可以可能到,使用多线程会把我们的系统搞得挺复杂的,是需要我们去处理很多事情的,为了防止多线程给我们带来的安全和性能的问题,下面就来简单总结一下我们需要哪些知识点来解决多线程遇到的问题
3.1简述解决线程安全性的方法
使用多线程就一定要保证我们的线程是安全的,这是最重要的地方。
在Java中,我们一般会有下面这么几种方法来实现线程的安全问题:
无状态(没有共享变量)
使用final使该引用变量不可变(如果引用变量也引用了其他的对象,那么无论是发布或者使用时都需要加锁)
加锁(内置锁,显示Lock锁)
使用JDK为我们提供的类来实现线程安全(此部分类就很多了)
原子性(就比如上面的count++,使用AtomicLong来实现原子性,那么在增加的时候就不会出现差错了)
容器(ConcurrentHashMap等等)
3.2原子性和可见性
何为原子性,何为可见性。
3.2.1原子性
在多线程中很多时候都是因为某个操作不是原子性的,使数据混乱出错。如果操作的数据是原子性的,那么就可以很大程度上避免了线程安全问题。
count++,先读取,后自增,再赋值。如果该操作是原子性的,那么就可以说是线程安全的(因为没有中间三步环节,一步到位)原子性
原子性就是执行某一个操作不可分割的,
就比如上面所说的count++操作,它就不是一个原子性的操作,它是分三个步骤的来实现这个操作的
JDK中有atomic包提供给我们实现原子性操作
也有人将其做成了表格来分类,看看
3.2.2可见性
对于可见性,Java提供了一个关键字:volatile给我们用
我们可以简单的认为:volatile是一种轻量级的同步机制
volatile经典总结:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性
我们将其拆开解释一下:
保证该变量对所有线程的可见性
在多线程的环境下:当这个变量修改时,所有线程都会知道该变量被修改了,也就是所谓的可见性
不保证原子性
修改变量赋值,实质上是在JVM中分了好几步,而在这几步内(从装载变量到修改)它是不安全的
使用volatile修饰的变量保证了三点:
一旦你完成写入,任何访问这个字段的线程将会得到最新的值
在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
volatile可以防止重排序(重排序指的是程序执行的时候,CPU、编译器可能会对执行顺序做一些调整,导致执行顺序并不是从上往下的。从而出现了一些意想不到的效果)。而如果声明了volatile,那么CPU、编译器就会知道这个变量是共享的,不会被缓存在寄存器或者其他不可见的地方。
一般来说,volatile大多用于标志位上(判断操作)满足下面的条件才应该使用volatile变量
修改变量时不依赖变量的当前值(因为volatile是不保证原子性的)
该变量不会纳入到不可变条件中(加锁就没必要使用volatile这种轻量级同步机制了)
3.3 线程封闭
在多线程的环境下,只要我们不使用成员变量(不共享数据),那么就不会出现线程安全问题了。
就用我们熟悉的Servlet来举例子,写了那么多的Servlet,从没有加锁,我们所有的数据都是在方法上操作的,每个线程都拥有自己的变量互不干扰。
在方法上操作,只要我们保证不要再栈(方法)上发布对象(每个变量的作用域仅仅停留在当前的方法上),那么我们的线程就是安全的。
再线程封闭上还有另一种方法,就是之前提过的ThreadLocal
使用这个类的API就可以保证每个线程自己独占一个变量。
3.4 不变性
不可变对象一定是线程安全的。
上面我们共享的变量都是可变的,正是由于可变的才会出现线程安全问题。如果该状态是不可变的,那么随便多个线程访问都是没有问题的。
Java提供了final修饰符给我们使用,final的身影我们可能就见得比较多了,但值得说明的是:
final不仅仅是不能修改变量的引用,但是引用里面的数据是可以改的。
1 final HashMap<Person> hashMap = new HashMap<>();
不可变对象引用在使用的时候还是需要加锁的。
或者把Person类也设计成是一个线程安全的类
因为内部的状态是可变的,不加锁或者Person不是线程安全类,操作都是危险的。
要想将对象设计成不可变对象,那么需要满足以下三个条件
对象创建后状态就不能修改
对象所有域都是final修饰的
对象是正确创建的,没有this引用逸出
String我们在学习的过程中我们就知道它是一个不可变对象,但是它没有遵循第二点,因为JVM在内部做了优化。但是我们如果要自己设计不可变对象,是需要满足三个条件的。
3.5线程安全性委托
很多时候我们要实现线程安全未必就需要自己加锁,自己来设计
我们可以使用JDK给我们提供的对象来完成线程安全的设计
非常多的工具类以后会介绍
四、最后
正确使用多线程能够提高我们应用程序的效率,同时给我们会带来非常多的问题,这些都是我们在使用多线程之前需要注意的地方。
无论是不变性、可见性、原子性、线程封闭、委托这些都是实现线程安全的一种手段。要合理地使用这些手段,我们的程序才可以更加健壮!
关于java基础知识点和java基础知识点思维导图的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于java基础基础知识点总结、JAVA基础知识、Java基础知识2-Java基本语法、Java基础知识_多线程必要知识点的相关知识,请在本站寻找。
本文标签: