本
文
摘
要
给大家分享一篇我之前在学习java过程中的关于java基础部分的笔记,比较详细,内容也比较多。 如有问题请指出以便修改,谢谢。 篇幅较长建议收藏浏览。
里面的部分重点内容以后会做详细讲解: *** ,线程,JVM,JMM内存管理,多线程。请大家多多关注。
java数据类型
Java数据类型
基本数据类型
1.1 char :Unicode编码的字符,或字符的整数编码,必须用单引号
float默认值是0.0f;
double默认值是0.0d;1.2基本类型字面值规则
1.整数字面值是int类型,如果右侧赋值超出int范围,需要做转型处理
2.byte,short,char 三种比int小的整数,在自身范围内可以直接赋值。
byte d=1+3 正确,1+3编译器会自动转成4
3.浮点数字面值是double;浮点数转成整数会直接舍弃小数点后位数。
4.字面值后缀,L D F
5.字面值前缀,0b 二进制;0x 16进制;0 8进制; \u char 类型16进制。
1.3基本类型的运算规则
1.计算结果的数据类型与运算中的最大类型一致。
2.byte,short,char三种比int小的整数,计算时会自动转成int
做加法运算时,数据类型会自动转成int,除了自增加自减不进行转化外,其它情况都是无long型时,所有非int类型转成int类型;有long类型时,都转成long类型。
char类型相加,提升为int类型。
计算
3.整数运算溢出。Integer.MAX_VALUE+1 的负数最小值
4.浮点数运算不精确
5.浮点数特殊值 infinity 整数除0 ;Nan 负数开方1.4 基本类型的类型转换
数字类型之间可以互相转换,从小到大自动转换,从大到小需要强制转型。
double d = 245; float d=100;自动转型。基本数据类型转换
1.5运算符
&& :逻辑与(短路与),两边同为真结果才为真,短路与:左边是假,右边忽略不执行
& :不管左边结果是什么都要执行右边(&的左右两边都要参与运算)
|| :逻辑或(短路或),两边只要有一个真结果就是真,短路或:左边是真,右边忽略不执行2.流程控制
2.1 switch:只能判断byte short char int enum jdk1.7之后的string。
从成立的case 无条件穿透所有的case包括default直到结束或者遇到break中断跳出循环;
如果所有条件都不成立,则执行default2.2 for循环
2.3 break 和 continue
Break 中断、跳出循环和switch
Continue 跳过后面的代码 继续进入循环的下一轮执行
2.4 for-each循环
数组遍历、 *** 迭代遍历的语法简化3.面向对象 —— 封装、继承、多态 ★ ★ ★ ★ ★
封装1 类:模板、图纸 。类中定义对象的属性数据(成员变量),方法(成员方法)
类在第一次使用时会加载到方法区
2 对象:从模板中创建的具体实例,实例是数据的打包
新建实例时,在堆内存中,新分配内存空间给这个实例。3 引用变量:理解成“遥控器”,保存一个实例的内存地址(引用变量保存在栈),引用变量的特殊值:null 不保存任何实例的内存地址。
4 构造方法:新建实例对象时,立即执行的一个特殊方法;构造方法必须和类同名,并且没有返回值类型。
一个类中必须有构造方法,自己没定义,系统会添加默认构造方法,构造方法一般用来给属性赋值
5 构造方法重载
一个类中可以定义多个不同参数的构造方法,是方法重载的一种体现。6 方法重载Overload:同名不同参,与返回值类型无关,所有方法都可以重载。
7 this关键字:this.xxx 特殊引用,引用当前对象的地址。
this(…):构造方法之间的调用,必须是首行代码,如果有多个构造方法,会通过
this(…)调取下面的所有构造方法,完成赋值。
注意this不能在静态方法中使用
继承
Java的继承是单继承多实现,只能继承一个父类(如果不继承其他类,默认继承object类),但可以实现多个接口
1.不能继承的有:构造方法,私有成员
过程:先新建父类对象,再新建子类对象,两者作为一个整体对象,调用成员时,先找子类,再找父类
2.方法重写:override
继承的方法,在子类中重新定义父类中的方法(只能在子类中重写),方法名相同,参数的个数和类型也必须相同,返回值类型也必须相同。
方法重写返回值类型如果是基本类型应与父类的一致;重写要求方法名完全相同,返回值类型如果是基本类型或无返回值时必须一致。
3.父类的构造方法
新建子类对象时会先新建父类对象,也会先执行父类的构造方法
默认执行父类的无参构造,默认隐含调用super();
new Student() 默认执行父类无参构造
new Student(……)默认执行父类无参构造
手动调用父类的有参构造,super(参数):父类没有无参构造时必须手动调用
4.super
Super.xxxx() 方法重写时,调用父类中同一个方法的代码
Super(参数) 调用父类的构造方法,默认调用父类无参构造super(),手动调用有参构造super(),必须是首行代码
注意super不能在静态方法中使用多态
一个对象具有多种形态的表现,多态的前提是必须有继承。
void test(父类型 o1) { };
把一个子类型的实例当做父类型来处理,所有的子类型都可以传递到该方法,被当做父类型处理;作用:一致的类型1. 类型的转换
A. 向上转型
子类的实例转成父类型,用父类型的引用变量,来引用子类实例,向上转型后,只能调用父类定义的通用成员,子类特有成员被隐藏
B. 向下转型
已经转成父类型的子类实例,转回子类型为了对子类型进行特殊处理
2. Instanceof 运行期的类型识别
当多种子类型都被当做父类型来处理,要对某种子类型进行特殊处理,可以先判断其真实类型再向下转型——对真实类型,及其父类型判断,都返回true。eg:if(obj instanceof CustomException){ throw new CustomException(obj.getMessage()); }4.数组
属于object类,用来存放一组数据的数据结构,数组是最基本的一种数据结构但不是基本数据类型,数组是相同数据类型组成的 *** ,数组中的元素按线性顺序排序
4.1 数组的创建
数组创建后若未指定初始值,则会依据数组类型的不同来设置默认值。。eg: 3种创业数组的方式:新建int[]数组,长度6,默认值都是0,数组的起始地址值保存在变量a。
int[] a = new int[4]; int [] b = {1,2,3,4}; int [] c = new int [] {1,2,3,4,5};4.2 数组的长度属性 a.length
数组一旦创立,长度不可变
最大下标 a.length-1
允许0长度的数组4.3 二维数组
存放数组的数组int[][] a = new int[3][2]; 外围长度为3,内部3个数组长度为2,一共有4个数组,内部数组默认值0,外围数组保存的是内部数组的地址。int[][] a = new int[3][]; 只建一个外围数组长度3,3个位置都是null,之后可以建新数组放入内部。4.4 Arrays 数组工具类
Arrays.toString(数组) 把数组数据连接成字符串。Arrays.sort(数组) 数组排序 基本类型:优化的快速排序;引用类型:优化的合并排序。Arrays.binarySearch(数组,目标值) 二分法查找,在有序数组中查找目标值下标,找不到返回 -(插入点+1)。Arrays.copyof(数组,长度) 复制数组成一个指定长度的新数组。4.5 数组 复制
Arrays.copyof(数组,长度) 复制数组成一个指定长度的新数组System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,复制的数量) ——不会创建新的数组,目标数组要事先存在。5. 变量
1 局部变量:定义在方法中或局部代码块中,必须初始化(第一次赋值时分配内存空间)
局部变量的作用域在定义它的大括号内有效,在作用范围内不能重复定义。
2 成员变量:定义在类中,自动初始化默认值,访问成员变量受访问控制符限制;局部变量可以和成员变量同名。6.Object类
如果一个类不继承其他类,则默认继承Object类
内部方法:
toString()获得一个对象的字符串表示。
Object中的默认实现是:“类名@地址”可在子类中重写toString方法。
equals() 当前对象与参与对象作比较是否相等。
a.equals(b) Object中的默认实现是比较内存地址。
Object中比较内存地址,基本类型默认比较内容值。
public boolean equals(Object obj) { return (this == obj); } // Object 中的equals的默认实现。7.String 类
String是封装char[] 数组的对象。
7.1 字符串创建
Char[] a ={‘a’,’b’,’c’}; String s = new String(a); >>>简易语法>>> String s = “abcd”7.2 字符串的常量池
String s1 = “abcd” 字符串的字面值写法。第一次使用一个字符串字面值时,会在字符串常量池中新分配内存,再次使用相同字面值时,直接访问常量池中存在的对象,而不会重复创建。7.3.字符串中的 equals 和 “==”
“==”比较内存地址
equals 看父类中的方法,object中的默认方法是比较内存地址,String类中重写了父类方法比较的是字符内容。如下说明:char[] a = {a,b,c,d}; String s1 = new String(a);//堆中新分配内存 String s2 = "abcd"; //在常量池新分配内存 String s3 = "abcd"; //访问常量池中存在的对象 System.out.println(s1==s2); //false 比较内存地址 System.out.println(s2==s3); //true 比较内存地址 String类中重写了equals方法,方法中比较的是字符内容 System.out.println(s1.equals(s2));//true 比较字符串内容 System.out.println(s2.equals(s3));//true 比较字符串内容7.4 字符串不可变且字符串连接效率低,每次连接都会新建字符串对象
7.5 字符串的常用方法charAt(i) 获取指定位置的字符length() 字符串长度,字符的数量indexof()找第一个子串出现的初始位置,找不到返回-1indexof(子串,start)从执行位置向后找lastIndexof(子串) 从后向前找subString(start)截取start到末尾subString[start,end )截取[start,end )范围trim()去除两端的空白字符matches()用来判断是否匹配正则表达式7.6 StringBuilder: 可变的字符序列,封装char[]数组,提供了一组方法,可以对内部封装的字符进行修改,常用来代替字符串做高效的字符串连接。
append() 追加字符内容,内部数组默认初始容量16,放满后翻倍+2;delete(start,end) 删除区间(start,end);deleteCharAt(i)删除指定位置 i;insert(i,内容) 在指定位置插入内容;insertCharAt(i,字符)在指定位置插入单个字符;replace(start,end,内容)替换指定范围的内容;StringBuilder和StringBufferStringBuilder:线程不安全,效率高;JDK1.5版本后的新类。StringBuffer:线程安全,旧版本的类。8.正则表达式
一般用来判断用户的输入内容是否符合格式要求
matches()字符串的方法,用来判断是否匹配 if(s.matches(regex)) { dosomething} split(正则):用匹配的子串来拆分字符串 String s = "aaa,bbb,ccc"; String[] a = s.split(",");replace(正则,子串)替换所有匹配的子串9.基本类型的包装类
把基本类型当做对象来使用
byte – Byte
short – Short
int – Integer
long – Long
float – Float
double – Double
char – Character
boolean – Boolean9.1 数字父类Number
子类:Byte,Short,Integer,Long,Float,Double,BigDecimal,BigInteger取出基本类型值的方法
byteValue(),shortValue(),intValue(),longValue(),floatValue(),doubleValu()9.2 Intger类
创建Integer对象: a= { value:6}
Integer a = new Integer(6);
Integer a = Integer.valueOf(6);
Integer 类中存在256个Integer缓存对象,封装-127到128;如果访问指定范围内的值,会访问缓存对象,如果超出范围,会新建对象。
9.3 Integer类的方法
字符串解析成int
Integer.parseInt();Byte.parseByte()……
Integer.toBinaryString() 转成二进制字符串
Integer.toOctalString() 转成八进制字符串
Integer.toHexString(255) 转成十六进制字符串
9.4 BigDcimal和BigInteger 类
BigDcimal精确的浮点数运算
BigInteger 超大的整数运算BigDecimal bd = BigDecimal.valueOf(2);
方法:
add(BigDecimal bd)subtract(BigDecimal bd)multiply(BigDecimal bd)divide(BigDecimal bd)divide(BigDecimal bd,保留位数,舍入方式)setScale(保留位数,舍入方式)9.5 自动装箱,自动拆箱
基本类型值,自动装箱成包装对象
Integer a = 6; 编译器编译成: Integer a = Integer.valueOf(6);自动拆箱(自动拆箱要注意null值)
int i = a; 编译器编译成: int i = a.intValue();10.抽象类 Abstract
半成品类,没有完成的类;抽象方法:没有代码,只有方法的定义,抽象类不能创建实例,主要用来被继承。
// 包含抽象方法的类一定是抽象类 public abstract class A { public abstract void f(); // 抽象方法 };抽象方法的作用:
作为通用方法,在父类中定义;要求子类,必须实现这个方法。
1)抽象类可以有自己的构造方法。
2)抽象类可以有具体的方法。
3)包含抽象方法的类一定是抽象类,必须使用abstract关键字修饰,这个方法必须由子类来实现。
4)抽象类不能使用new关键字来创建实例。
5)当一个类中只要有一个抽象方法,这个类就必须是抽象类。
6)抽象类可以定义实例变量和静态变量以及常量。
7)抽象类可以再继承抽象类,也可以继承普通的类。
11.关键字 final
内存地址不可变,可以修饰常量、类、方法
11.1 final 常量:值不可变,但引用类型因为保存的是地址,所以内容可以变。
final Point a = new Point(3,4);
a.x = 30;//对
a.y = 40;//对
11.2 final 方法不能在子类重写,但可以被继承。;final不能用于修饰构造方法,父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
11.3 final 类 不能被继承,没有子类
Static — 静态 共享的数据
静态成员属于类,而不属于实例
静态成员
用类来调用静态成员 Soldier.count
实例成员
用实例来调用实例成员 s1.id
工具方法
Math.Random() Arrays.toString() String.valueOf()
静态方法中不能直接调用实例的成员(非静态),只能用实例调用。
class A { public static void main(String[] args) { f();//静态调静态 } static void f() { g();//错,静态不能直接调用非静态 A a = new A(); a.g();//只能用实例调用 } void g(){ } }静态变量保存在方法区类的空间中,只保存一份可以在所有实例 *** 享的数据
对象的加载过程 ★ ★ ★ ★ ★
加载类:
1.加载父类,为父类静态变量分配内存 – 后台执行不可见
2. 加载子类,为子类静态变量分配内存
3. 执行父类静态变量的赋值运算,和静态初始化块
4. 执行子类静态变量的赋值运算,和静态初始化块
新建实例:
5. 新建父类实例,为父类实例变量分配内存
6. 新建子类实例,为子类实例变量分配内存
7. 执行父类的实例变量赋值运算
8. 执行父类的构造方法
9. 执行子类的实例变量赋值运算
10. 执行子类的构造方法
12. ***
用来存放一组数据的数据结构
数组的缺点: 长度固定 ; 访问方式单一只能下标访问; 前面增删数据操作繁琐
*** 的继承结构:(初学者了解常用的就可以了)Collection 是对象 *** , Collection 有两个子接口 List 和 Set,
List 可以通过下标 (1,2…) 来取得值,值可以重复,而 Set 只能通过游标来取值,并且值是不能重复的
ArrayList , Vector , LinkedList 是 List 的实现类
ArrayList: 是线程不安全的, Vector 是线程安全的,这两个类底层都是由数组实现的
LinkedList:是线程不安全的,底层是由链表实现的
Map: 是键值对 ***
HashTable 和 HashMap 是 Map 的实现类
HashTable:是线程安全的,不能存储 null 值
HashMap:不是线程安全的,可以存储 null 值
ArrayList
数组列表,封装了一个数组,及其操作代码和更便捷的方法,内部数组默认初始容量10 放满后,1.5倍增长
方法:
add(数据)— 添加数据;get(int i)—访问指定下标数据; remove(int i)移除指定位置的数据,返回被移除的数据; remove(数据)— 找到第一个相等的数据,找到移除并返回true,找不到返回false; size() 元素的数量;iterator() 辅助新建迭代器
效率:
访问任意位置效率高,增删数据的效率可能降低。
LinkedList — 双向链表
方法
和ArrayList有相同的方法
LinkedList 两端数据操作方法
addFirst(数据);addLast(数据);getFirst();getLast();removeFisrt()
removeLast();效率
两端效率高HashMap — 哈希表、散列表 (面试必问) ★ ★ ★ ★ ★
存放键值对的数据,用键来快速定位数据,来提取键对应的值
键:不重复,无序
Hashmap中的key-value都是储存中entry数组中的
Hashmap的实现不是同步的,意味着它不是线程安全的
Hashmap的实例有两个参数影响其性能:初始容量,和加载因子
方法:
put(key,value)放入键值对数据,重复的键会覆盖旧值
get(key)获得键对应的值,键不存在,得到null
remove(key)移除键值对数据,返回被移除的值
size()键值对的数量。
哈希运算过程
HashMap内部,使用entry[]数组存放数据 数组默认初始容量16 放满后容量翻倍+2 key.hashCode()获得键的哈希值 用哈希值和数组长度,运算产生下标值 i 新建entry对象来封装键值对数据 Entry对象,放入i 位置 如果是空位置,直接放入 如果有数据,一次equals()比较是否相等 找到相等的,覆盖旧值 没有相等的,链表连接在一起 负载率、加载因子超过0.75 新建翻倍容量的新数组 所有数据,重新执行哈希值,存入新数组 Jdk 1.8 链表长度到8,转成红黑树 树上的数据减少到6,转回成链表hashCode()
hashCode()是object的方法,默认实现是用内存地址作为哈希值,
可以重写方法来获得相同的哈希值
哈希运算中要有相同的哈希值,才能保证计算出相同下标值,并且要equals()也要相等(equals方法也要重写),才可以覆盖旧值,否则会链表连接。
重写hashCode的惯用算法:(x.y是变量的值)
13.异常
封装错误信息的对象
错误信息:类型、提示消息、行号异常的继承结构
捕获异常
try { } catch(AException e) { } catch(BException e) { } catch(父类型Exception e) { } finally { 不管出不出错,都会执行 } 如果抛出异常,并且中catch中有return语句,这个return语句会先执行,执行之后将结果保 存在缓存中,再去查看是否有finally,如果有finally就先执行finally语句,之后再返回缓存中 return的值,如果finally中也有return,那么finally中的return会覆盖掉之前缓存中的return, 即最终会返回finally中的return值throw 手动抛出异常,执行异常的抛出动作 类似 return;当程序出现逻辑错误,不自动创建并抛出异常,可以手动判断逻辑错误,手动创建异常对象并抛出。
底层的异常往上层抛,在上层处理。if(...) { AException e = new AException(); throw e; }异常包装
捕获的异常对象,包装成其他类型再做抛出,多种类型简化成一种类型,不能抛出的异常包装成能抛出的异常再抛。
RuntimeException 和 其他Exception
RuntimeException— 非检查异常,编译器不检查是否有异常处理代码,存在默认的抛出管道
其他异常 — 编译器检查是否有处理代码,不处理,不能编译。
14.接口
极端的抽象类,结构设计工具,用来解耦合,隔离现实
Implements代替extends
Interface 代替class
接口的定义:
公开的抽象方法 公开的常量 公开的内部类、内部接口
1)接口只能定义常量
2)接口只能定义抽象方法
3)接口只能继承接口,不能继承普通的类和抽象类
4)接口没有构造方法
注意:
1)在接口中定义常量时,可以不用final static修饰,因为编译器在编译时会自动加上。
2)在接口中定义抽象方法时可以省略abstract关键字,编译器在编译时同样会加上。
3)JDK1.8 中的接口可以有默认的方法实现,详情参见:interface Map。
class A implements X,Y,Z {} class A extends B implements X,Y,Z {} // 类可以同时实现多个接口 interface A extends X,Y,Z {} // 接口和接口的继承15 文件、字符操作流
File
封装一个磁盘路径字符串,提供了一组对文件、文件夹的操作方法,可以封装文件夹路径、文件路径、不存在的路径。 {path=“d:/abc”}
方法getName() 获取文件名
getPatrent() 获取父目录
getAbsolutePath()完整路径
length() 文件字节量,对文件夹无效,会返回假数据
isFile() 判断是否是文件
isDirectory()是否是文件夹
创建、删除
createNewFile()新建文件,文件已存在不会新建,返回false;文件夹不存在会出现异常
mkdirs()逐层创建多层文件夹
delete()删除文件、空目录
目录列表
list()得到String[] 包含所有文件名 [“a.txt”, “b.mp3”, “c.jpg”]
listFiles() 得到 File[],包含所有文件的封装的File对象 [{…}, {…}, {…}]
流 Stream
数据的读写操作(io操作),抽象成数据在管道中流动
单方向流动
输入流,只能用来读取数据(读入内存)
输出流,只能用来输出数据(内存数据向外输出)
只能从头到尾,顺序流动一次,不能反复流动,如果要重复流动,可以重新创建新的流
InputStream,OutputStream 字节流的抽象父类
方法:
write(int b) 只输出int四个字节中,末尾的一个字节值 [1][2][3][4] —> [4]
write(byte[], start, length) 输出byte[] 数组中,从start开始的length个字节值
read() 读取一个字节值,补三个0字节,变成int [4] —> [1][2][3][4],读取结束后,再读取会返回 -1。
read(byte[] buff) 按数组的长度,读取一批字节值,存放到指定的数组中,并返回这一批的字节数量,读取结束后,再读取会返回 -1。
FileInputStream,FileOutputStream — 文件流
ObjectInputStream,ObjectOutputStream —对象序列化、反序列化
序列化 把一个对象的信息,按固定的字节格式,变成一串字节序列输出
方法:
writeObject(Object obj) 把对象变成一串字节序列输出
readObject() 读取序列化数据,反序列化恢复对象
Serializable 接口——被序列化的对象,必须实现 Serializable 接口
不序列化的变量
Static — 属于类,不随对象被序列化输出
Transient —临时,只在程序运行期间,在内存中存在,不会被序列化持久保存
字符编码
ASC-II 0到 127,英文、指令字符
iso-8859-1 Latin-1 西欧编码 ,把ASC-II扩展到255
CJK 编码 亚洲编码,中日韩
GBK 国标码 英文单字节,中文双字节
Unicode 万国码 常用表,双字节 生僻字 三字节或四字节
UTF-8 Unicode 的传输格式 Unicode Transformation format英文,单字节某些字符,双字节;中文,三字节;特殊符号,四字节
Java的char类型是 Unicode
Java的转码运算
InputStreamReader,OutputStreamWriter
字符编码转换流 OutputStreamWriter — 把Java的Unicode编码字符,转成其他编码输出
InputStreamReader —读取其他编码字符,转成Unicode字符16 内部类
定义在类内部、方法内部或局部代码块内部的类,用来辅助外部实例运算,封装局部数据,或局部的运算逻辑。
非静态内部类、属于实例的内部类 非静态内部类实例,必须依赖于一个外部类的实例才能存在。
静态内部类 静态内部类,与普通的类没有区别。局部内部类
局部定义的类型,类似于局部变量,有作用范围,只能在局部代码块内使用这种类型
局部内部类中,使用外面的局部变量,必须加 final,jdk1.8,缺省。匿名内部类
Weapon w = new Weapon() {...}; {} - 匿名类
new - 新建匿名类的实例
Weapon - 父类型
() - super(),可传参数super(1,2,3)17. Java内存管理
堆内存 用来存放由new创建的对象实例和数组。Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。
栈内存 保存的是堆内存空间的访问地址,或者说栈中的变量指向堆内存中的变量(Java中的指针)
栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。常量池存在于堆中(1.7b后的新版本)。
普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。
方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
18.线程
在进程内部,并行执行的任务
创建线程(两种方式)
继承 Thread
实现 Runnable继承 Thread
编写 Thread 的子类,并重写 run() 方法。启动之后,自动运行 run() 方法中的代码
实现 Runnable
实现 Runnable 接口,实现它的 run() 方法,Runnable 封装在线程中执行的代码,新建线程对象时,把Runnable对象放在线程内,启动
线程的状态
线程的方法
Thread.currentThread() 获得正在执行的线程实例
Thread.sleep(毫秒值) 让正在执行的线程,暂停指定的毫秒值时长
getName(),setName() 线程名
start() 启动线程 interrupt() 打断线程的暂停状态
join() 当前线程暂停,等待被调用的线程结束
setDaemon(true) 后台线程、守护线程
JVM虚拟机退出条件,是所有前台线程结束,当所有前台线程结束,虚拟机会自动退出
不会等待后台线程结束 例如:垃圾回收器是一个后台线程。
线程同步 synchronized
让多个线程共享访问数据时,步调一致的执行。一个线程修改时,其他线程等待修改完成后才能执行;一个线程访问时,其他线程等待访问结束
任何实例,都有一个“同步锁”,synchronized 关键字,要求一个线程必须抢到同步锁才能执行synchronized(对象) { 共享的数据访问代码 } --抢对象的锁 synchronized void f() { } -- 抢当前实例的锁 static synchronized void f() { } --抢类的锁