摘自 Java in a Nutshell (java技术手册)
保留字:
java语言没有使用const 和 goto,但是它们也是保留字。
标识符:
长度不限,不能以数字开头,一般不能包含标点符号,但是可以包含ascii字符集中的下划线(_)和美元符号($),以及unicode字符集中的其他货币符号,如¥,£。其中货币符号主要用于自动生成的源码中。 其中也可以使用utf-8标识符,汉字什么的。如:
//Ech0
public class Main {
public static void main(String[] args) {
String 小明="小明zzz";
System.out.println(小明);//输出 小明zzz
}
}
基本数据类型:
java中,如果整数超出指定整数类型的范围,不会上溢或下溢,而是直接回绕。如
//Ech0
public class Main {
public static void main(String[] args) {
byte a=127,b=1;
byte sum = (byte)(a+b);
System.out.println(sum);//结果为 -128
}
}
每个整数类型都有对应的包装类,均有定义MIN_VALUE,MAX_VALUE常量。表示相应的取值范围。如
//Ech0
public class Main {
public static void main(String[] args) {
System.out.println("max:"+Byte.MAX_VALUE+"\nmin:"+Byte.MIN_VALUE);
}
}
Output:
max:127
min:-128
java中浮点数默认为double类型,为float类型变量赋值浮点数需要在数字后面加上f或者F。
java浮点数类型能处理到无穷大的上溢以及到零的下溢,因此浮点数运算从不抛出异常。
java浮点数区分正零和负零,取决于从哪个方向出现的下溢。但在实际使用中正零和负零表现基本一致。区分方法用1除以它,如果的到的是正无穷,则为正零,若为负无穷则为负零。
java中浮点数运算不合法得的情况下得到的是NaN (Not a number) ,如0.0/0.0 。
同样的浮点数也有对应的包装类: Flaot ,Double。其中也定义了一些有用的常量:MIN_VALUE, MAX_VALUE, NEGATIVE_INFINITY, POSITIVE_INFINITY , NaN。
//Ech0
public class Main {
public static void main(String[] args) {
double inf =1.0/0.0;
double neinf=-1/0.0;
double nezero = -1/inf;
double NaN = 0.0/0.0;
System.out.println("inf ="+inf+"\nneinf ="+neinf+"\nnezero ="+nezero+"\nNaN="+NaN);
}
}
Output:
inf =Infinity
neinf =-Infinity
nezero =-0.0
NaN=NaN
运算过程中的自动放大特性:
对于算术运算符,自增,自减,位运算和位移运算来说如果至少有一个操作数为double类型,那么返回值就是double类型;如果至少有一个操作数为float类型,返回值就是float类型:如果至少有一个是long类型则返回值是long类型;除此自外都返回int类型。即便两个操作数都是byte、short、或者char类型,也会放大为int类型。
数组的声明:
为了兼容c,c++,java还支持 将中括号放在变量名后面,元素类型后面可以放也可以不放中括号。
String [][] a;
String b[][];
String [] c[];//这三行代码声明的字段属于同一种数组类型,且均合法。
数组的创建与初始化:
int [] intArray=new int [1024];//使用这种句法创建的数组每个元素都会自动初始化,初始化和类中字段默认值相同。
//boolean 类型为false,char类型为 \u0000 ,整数类型为 0,浮点数类型为0.0,引用类型为null。
int [][][]mulArray=new int [1024][][];//用new创建多维数组时无需指定所有维度的大小,只要最左边的几个维度(至少指定左边第一个)指定大小即可。
//且如果只为数组的部分维度指定大小,这些维度必须位于最左边。
int [] num=new int[]{1,2};
int [] num={1,2}; //两种方式创建的数组完全一样,不同在于,第一种方式会在程序编译时创建和初始化,而第二种方式是在程序运行时才创建和初始化。
数组的索引表达式必须是int类型,或者能放大转换成int的类型:byte,short,甚至是char。如果使用long类型的表达式索引数组,即便运行时表达式的返回值在int类型的取值范围内,也会导致编译出错。数组的length字段是int类型,所以数组中的的元素数量不能超过Integer.MAX_VALUE(即 2^31-1)。
数组的使用:
所有数组类型都实现了Cloneable接口,任何数组都能调用clone()方法复制自己。注意,返回值必须校正成适当的数组类型。还有clone()方法执行的是浅复制,也就意味着如果数组元素是引用类型,那么只会复制引用,而不会复制引用的对象。如:
//Ech0
class temp {
int a, b;
temp(int a, int b) {
this.a = a;
this.b = b;
}
void show() {
System.out.println("a: " + this.a + " b:" + this.b);
}
}
public class Main {
public static void main(String[] args) {
temp[] t = new temp[] { new temp(1, 2), new temp(2, 3) };
temp[] copy = t.clone();
t[0].show();
copy[0].show();
t[0].a = 233;
t[0].show();
copy[0].show();
}
}
Output:
a: 1 b:2
a: 1 b:2
a: 233 b:2
a: 233 b:2//后面两个输出结果一致说明引用的是同一个对象
处理对象和引用副本:
对于基本类型的变量,在执行简单的变量间赋值操作时,会复制对应值的副本。而对于引用类型的变量,只会复制引用,而不会分配新的空间,即不会复制其对象的副本。如:
public class Ech0 {
public static void main(String[] args) {
int a =32;
int b=a;
a++;
out.println("a= "+a+"\nb= "+b);
char [] hello ={'h','e','l','l','o'};
char [] charArray=hello;
charArray[0]='~';//修改charArray中的内容
System.out.println(hello);
System.out.println(charArray); //输出结果一致。说明引用的是同一个对象。
}
}
output:
a= 33
b= 32
~ello
~ello
同样,把基本数据类型和引用类型作为方法的参数时也有类似的区别。
class point {
protected int x, y;
point(int a, int b) {
x = a;
y = b;
}
protected void show() {
System.out.println(" (" + x + "," + y + ")");
}
}
public class Ech0 {
static void increase(int num) {
num++;
}
static void increase(point num) {
num.x++;
}
public static void main(String[] args) {
int a = 32;
point b = new point(22, 33);
increase(a);//a中的值不会改变,运行完后仍是32
out.println(a);
increase(b);//b引用的对象的内容改变了,b.x = 23
b.show();
}
}
output:
32
(23,33)
可以这样理解,在将基本数据类型作为方法的参数时,会把实参的值传递给形参,而在将引用类型作为参数时,传递的是引用。
java文件结构:
一个java文件中最多只能有一个声明为public的顶层类。public类的目的是供其他包中的类使用。但是在一个类中,声明为public的嵌套类或者内部类数量不限。
类:
类字段(静态成员变、常量) 会在类的构造方法之前初始化。 javac会为每一个类自动生成一个类初始化方法,类字段在这个方法的主体中初始化,这个方法只在首次使用类之前调用一次。(通常是在java虚拟机首次加载类时)。
子类对象可以直接当做超类(父类)对象使用而无需校正(自动放大转换),object类是所有类的父类。
子类的构造方法必须调用父类的某个构造方法,如果子类构造方法的第一个语句没有使用this()或者super()显式调用另一个构造方法javac编译器会自动插入super()。如果父类没有无需参数的构造方法,这种隐式调用会导致编译出错。
只要创建对象,就回调用一系列构造方法,从子类到父类,一直向上,直到类层次结构的顶端object类为止。因为父类的构造方法始终在子类的构造方法的第一个语句中调用,所以object类的构造方法始终最先运行,然后运行object的子类的构造方法,就这样沿着类层次结构一直向下,直到实例化的那个类为止。
System类不能被实例化: 构造函数是私有的,且没有提供对应的实例接口。
当子类中定义了与父类同名的字段时,父类中的同名字段会被 遮盖 ,可以通过super关键字来调用父类的同名字段,还可以将子类校正为父类再访问字段:
class A{
int a;
}
class B extends A{
int a;
}
class C extends B{
int a;
}
对于上叙类的定义,在c类方法中可以按照下面的方式引用这些不同的字段:
a//C类中的a字段
this.a//C类中的a字段
super.a//B类中的a字段
((B)this).a//B类中的a字段
((A)this).a//A类中的a字段
类方法(静态方法) 可以被遮盖,但不能被覆盖(重写)。
class A {
int a;
void show() {
System.out.println("A");
}
}
class B extends A {
int a;
@Override
void show() {
System.out.println("B");
}
}
class C extends B {
int a;
@Override
void show() {
System.out.println("C");
}
}
public class Ech0 {
public static void main(String[] args) {
A tmp =new C();
tmp.show();//输出结果为C , 如果将A中的show方法注释掉,则会编译出错。
}
}
对于上述代码,尽管tmp声明为
A类,但里面存放的是C类对象,调用show方法时仍会调用C类里面对应的show方法。
javac生成的字节码在运行时使用虚拟方法查找(virtual method lookup),即解释器解释表达式tmp.show()时会检查变量tmp引用的对象的真正运行时类型,然后找到适用于这个类型的show()方法。
但是可以在子类的 内部 (注意是内部!)使用super调用直接父类中被覆盖(重写)的方法
解释器使用super句法调用实例方法时,会执行一种修改过的虚拟方法查找。第一步和常规虚拟方法查找的一样,先确定调用方法的对象属于哪一个类。正常情况下,运行时会在这个类中寻找对应的方法定义。但是,使用super句法调用方法时,先在这个类的超类中查找,如果超类直接实现了这个方法,那就调用这个方法。如果超类继承了这个方法,那就调用继承的方法。(如果既没实现又没继承,则会编译出错)
接口:
接口中所有成员都隐式使用public声明,如果在接口中使用protected 或者 private 定义方法 会导致编译出错。
从 java 8开始,接口中可以包含静态方法。 并且 可以使用 default 关键字修饰方法,并提供默认实现,使其标记为可选方法,即实现该接口时可以不实现被标记的方法,而使用默认的实现。
接口可以继承多个接口。一个类可以实现多个接口。
标记接口:定义全空的接口。(为对象提供额外信息)如 java.io.Serializable (表示实现该接口的类支持安全序列化), java.util.RandomAccess (表示实现该接口的类支持快速随机访问)
泛型 :
就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)故又名 参数化类型 (parameterized type)
菱形句法 :
List<String> list = new ArrayList<>();//在java的现代版本中可以使用这种句法省略重复的类型
类型擦除:
泛型是一种编译时技术,在运行时不包含类型信息,仅在class的实例中包含类型参数的定义信息。泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以把它认为是一个从源码到源码的转换,它把泛型版本转换成非泛型版本。基本上,擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个List<String>类型被转换为List。所有对类型变量的引用被替换成类型变量的上限(通常是Object)。而且,无论何时结果代码类型不正确,会插入一个到合适类型的转换。
类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。
为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。如:
ArrayList list = new ArrayList();//此时list中的类型参数为object
一个泛型类被其所有调用共享,一个泛型类的所有实例在运行时具有相同的运行时类(class),而不管他们的实际类型参数。事实上,泛型之所以叫泛型,就是因为它对所有其可能的类型参数,有同样的行为;同样的类可以被当作许多不同的类型。作为一个结果,类的静态变量和方法也在所有的实例间共享。这就是为什么在静态方法或静态初始化代码中或者在静态变量的声明和初始化时使用类型参数(类型参数是属于具体实例的)是不合法的原因。
泛型中的注意事项:
①不能用基本类型实例化类型参数(可以使用对应包装类)
②不能抛出也不能捕获泛型类实例(泛型类扩展Throwable即为不合法,因此无法抛出或捕获泛型类实例。但在异常声明中使用类型参数是合法的。)
③参数化类型的数组不合法
④不能实例化类型变量
⑤泛型类的静态上下文中不能使用类型变量
枚举类型:
简单枚举类型:
enum num{
one,two,three
}
public class enumtest {
public static void main(String [] args){
num tmp=num.one;
num a=num.three;
System.out.println(tmp.name());//返回此枚举常量的名称,在其枚举声明中对其进行声明。
System.out.println(tmp.toString());//返回枚举常量的名称,它包含在声明中。
System.out.println(tmp.ordinal());//返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
System.out.println(tmp.getDeclaringClass());//返回与此枚举常量的枚举类型相对应的 Class 对象。
System.out.println(tmp.compareTo(a));//比较此枚举与指定对象的顺序。在这里 返回值=0-2=-2
}
}
output:
one
one
0
class ech0.num
-2
带有自定义字段和方法的枚举:
enum num{
one(1),two(2){
@Override
public boolean isEven(){//重写isEven方法
return true;
}
},three(3);
private final int value;
private num(int value){//此处不能为public,可以为default,构造方法只能有一个
this.value=value;
}
public int getValue(){
return value;
}
public boolean isEven(){
return false;
}
}
public class enumtest {
public static void main(String [] args){
num first=num.one;
num second=num.two;
System.out.println(first.getValue());
System.out.println(second.getValue());
System.out.println(first.isEven());
System.out.println(second.isEven());
}
}
output:
1
2
false
true
枚举类型都隐式扩展java.lang.Enum 类
不能被泛型化
可以实现接口
不能被扩展(继承)
如果枚举中的所有值都有实现主体,那么只能定义为抽象方法
只能有一个私有(或者默认访问权限)的构造方法
注解:
一种特殊的接口。作用是注解java程序的某个部分。
例如 @Override注解,注解没有直接作用,它只是为注解的方法提供额外的信息,注明这个方法重写了父类的方法。
注解可以为 IDE 提供有用的提示。如果开发者把方法的名称写错了,而这个方法本来是要覆盖父类的方法,那么,在这个名称拼错的方法上使用@Override 注解,可以提醒编译器什么地方出错了。
嵌套类型(内部类):
静态成员类型(静态成员内部类):
类名不能与任何外部类同名
静态成员类型和所在类的任何实例不关联
静态成员类型只能访问所在类的静态成员
静态成员类型能访问所在类型中的所有静态成员(包括其他静态成员类型)
嵌套的接口,枚举,和注解都隐式声明为静态类型
静态成员类型可以在顶层类型中定义,也可以嵌入任何深度的其他 静态成员 类型中
静态成员类型不能在其他嵌套类型中定义
package ech0;
public class Outer {
private static int staticNum=0;
private int genNum=0;
public static class Inner1{
private static int staticNum1=0;
private int genNum1=0;
public void show(){
System.out.println(staticNum1);
System.out.println(staticNum);//访问外部类的静态私有成员
//System.out.println(genNum);//非法语句,不能访问外部类的非静态成员
System.out.println(Inner2.num2);//访问其它静态内部类的静态私有成员
}
}
public static class Inner2{
private static int num2=2;
public static class Inner2sIn{
private static int num3=3;
//在静态成员内部类内部定义的静态成员内部类
}
}
public static void main(String [] args){
System.out.println(Inner1.staticNum1);//外部类访问内部类的静态私有成员
//System.out.println(Inner1.genNum1); 非法语句,不能访问内部类的非静态成员
System.out.println(Inner2.Inner2sIn.num3);//外部类访问静态内部类的静态内部类的静态成员
Outer.Inner1 in=new Outer.Inner1();//创建一个静态成员内部的实例
}
}
静态成员类型可以访问所在类型中的所有静态成员,包括私有成员,反过来也是成立的:所在类型的方法中可以访问静态成员类型中的所有静态成员。
静态成员类型甚至可以访问任何其他静态成员类型中的所有静态成员。
非静态成员类(非静态成员内部类):
一个非静态成员类的实例始终关联一个外层类型的实例
非静态成员类的代码能访问外层类型的所有字段与方法(静态与非静态的都可以访问)
类名不能与任何外层类或者包同名
非静态成员类不能含有任何静态字段,方法或者类型,不过可以包含同时使用static和final声明的常量字段
package ech0;
class A{
public String name= "A";
public String demo="demo";
}
public class Outer extends A{
public String name= "Outer";
class Inner{
private String name= "Inner";
// private static int a=0; 不能含有静态字段,方法,或者类型
private final static int num=0;//但是可以包含同时使用static final 修饰的静态常量字段
void show(){
System.out.println(name); //访问内部类inner的name字段
}
void showOuter(){
System.out.println(Outer.this.name);//访问outer类中的name字段 ,当外部类的字段被内部类的同名字段遮盖才有必要使用这种句法
//这里Outer中的name字段遮盖了A类中的name字段
System.out.println(demo);//demo字段继承自A,且内部类无同名字段,直接访问。
}
}
public static void main(String [] args){
Outer out = new Outer();//创建一个外部类的实例
Outer.Inner in = out.new Inner();//由外部类实例继而创建一个内部类的实例。
Outer.Inner tmp=new Outer().new Inner();//创建一个内部类的实例,但是并没有为其对应的外部类赋予名称
in.show();
in.showOuter();
}
}
局部类:
在方法体或语句块(包括方法、构造方法、局部块或静态初始化块)内部定义的类成为局部内部类
局部类和外层实例关联,而且能访问外层类的任何成员,包括私有成员
还可以访问局部方法的作用域中声明为final的任何变量,方法参数,异常参数
局部类的名称只存在于 定义它的块中,在块的外部不能使用(但是要注意,在类的作用域中创建局部类的实例,在这个作用域之外仍能使用)
局部类不能声明为public 、protect、private、 或者static
局部类不能包含静态字段、方法或者类。唯一的例外是同时使用final 和static 声明的常量
接口 、枚举、和注解类型不能局部定义
不能与任何外层类同名
局部类的生命周期可能比定义它的方法执行时间长很多
在方法体外不能创建局部类的实例
static 方法中定义的局部类可以访问外部类定义的任何static成员
package ech0;
class X{
public char x = 'a';
}
class Y{
public char y='y';
}
public class LocalDemo extends X {
private String outer_var="outer_var";
public void demo(final int e){
final String demo_str="demo_str";
int a=0;
class Local extends Y{
void show_Allinfomation(){
//System.out.println(a); 非法,不能访问
System.out.println(demo_str);//访问声明为final的局部变量
System.out.println(outer_var);//访问外部类的私有字段
System.out.println(y);//访问继承自Y类的字段
System.out.println(x);//访问外部类继承自X类的字段
System.out.println(e);//访问声明为final的方法参数
}
}
Local temp= new Local();
temp.show_Allinfomation();
}
public static void main(String []args){
LocalDemo e=new LocalDemo();
e.demo(0);
}
}
如果创建了一个局部类的实例,那么jvm执行完定义这个类的代码块后,实例不会自动消失。因此即便这个类在局部定义,但这个类的实例能跳出定义它的地方:
package ech0;
public class LocalClass_character {
public static interface Holder{
int getValue();
}
public static void main(String []args){
Holder[] holder=new Holder[10];
for(int i=0;i<10;i++){
final int value=i;
class Local implements Holder{
@Override
public int getValue (){
return value;
}
}
holder[i]=new Local();
} //for 循环外
/*
局部类不在作用域中,因此不能使用,但是holder数组中保存了10个局部类的实例
变量value不在作用域中,但是 存在于那十个对象的getValue的作用域中
因此可以在每个对象上调用gerValue方法 ,打印value的值
*/
for(int i =0;i<10;i++){
System.out.println(holder[i].getClass());
System.out.println(holder[i].getValue());
}
}
}
这种局部类有时也叫闭包(closure)。用更一般java的术语来说,闭包是一个对象,它保存作用域的状态,并让这个作用域在后面可以继续使用。
匿名类:
匿名类没有名称,故不可以在类主体中定义构造方法
定义匿名类时,在父类后面的括号中指定的参数,会隐式的传给父类的构造方法。匿名类一般用于创建构造方法不接受任何参数的简单子类,所以在定义匿名类的句法中,括号经常都是空的
匿名类就是一种局部类 ,两者的限制一样
可以使用初始化程序代替构造方法
package ech0;
interface DemoAno{//带有默认实现方法的接口
default void print(){
System.out.println("This a demo!");
}
}
public class Anonymous {
static void show(DemoAno A){
A.print();
}
public static void main (String [] args){
show(new DemoAno(){//实现该接口的匿名类
public void print(){//重写接口中的方法
System.out.println("this a aninymous class!");
}
});
}
}
对java解释器而言,并没有所谓的嵌套类型,所有类都是普通的顶层类。
为了让嵌套类型看起来是在另一个类中定义的,java编译器会在它的生成的类中插入隐藏字段,方法,和构造方法参数。这些隐藏字段和方法称为合成物(synthetic)。
因为嵌套类型编译成普通的顶层类,所以不能直接访问外层类型中有特定权限的成员。因此,如果静态成员类型使用了外层类型的私有成员(或者具有其他权限的成员),编译器会生成合成的访问方法(具有默认的包访问权限),然后把访问私有成员的表达式转换成调用合成方法的表达式。
非静态成员类的实现
非静态成员类的每个实例都和一个外层类的实例关联,为了实现这种关联,编译器为每个成员类定义一个名为this$0的合成字段,这个字段的作用是保存一个外层实例的引用,编译器为每个非静态成员类的构造方法提供了一个额外的参数,用于初始化这个字段。每次调用成员类的构造方法时,编译器会自动把这个额外参数的值设为外层类的引用
局部类和匿名类的实现
局部类之所以能访问外层类的字段和方法,原因和非静态成员类一模一样。
局部类之所以能使用局部变量,是因为javac自动为局部类创建了私有的实例字段,保存局部类用到的各个局部变量的副本。
编译器还在局部类的构造方法中添加了隐藏的参数,初始化这些自动创建的私有字段。其实局部类没有访问局部变量,真正访问的是局部变量的私有副本。
单例模式:
确保不能有超过一种创建实例的方式,且要保证不能获取处于未初始化状态的对象引用。
public class Singleton {
private static final Singleton INSTANCE= new Singleton();
static boolean initialized = false;
private Singleton(){
}
private void init(){
//初始化
}
public static synchronized Singleton getInstance(){
if (initialized) return INSTANCE;
INSTANCE.init();
initialized = true;
return INSTANCE;
}
}
异常:
Jvm中的内存管理:
jvm确切知道它分配了那些对象和数组,这些对象和数组存储在某种内部数据结构中,我们称这种数据结构为分配表(allocation table)。jvm还能区分每个栈帧(stack frame)里局部变量指向堆里的哪个数组或对象,最后jvm能追踪堆中对象和数组保存的引用,不管引用躲么迂回,都能找到所有仍然被引用的对象和数组。
标记清除(mark and sweep):
①迭代分配表,把每个变量都标记为“已死亡”
②从指向堆的局部变量开始,顺着遇到的每个对象的全部引用向下,每遇到一个之前没见过的对象或者数组,就把它标记为“存活”。像这样一直向下,直到找出能从局部变量到达的所有引用为止。
③再次迭代分配表,回收所有没有标记为“存活”的对象在堆中占用的内存,然后把这些内存放回可用内存列表中,最后把这些对象从分配表中删除。
筛选回收 (evacuation):
这种方式把堆内存分成多个独立的内存空间,每次回收垃圾时,只为活性对象分配空间,并把这些对象移到另一个内存空间。
java 中 ,一个进程中的每个java应用线程都有自己的栈(和局部变量),不过这些线程共用一个堆,因此可以轻易的在线程之间共享对象。
对象默认可见 ,假如我有一个对象的引用,就可以复制一个副本,然后将其交给另一个线程,不受任何限制。
对象是可变的 ,对象的内容(实例字段的值)一般都可以修改。
volatile 关键字
用于并发访问数据。这个关键字指明,应用代码使用字段或者变量之前,必须重新从主存内读取数据。同样,修改使用volatile 修饰的值后,在写入变量之后,必须存回主内存。 volatile 主要用途之一是在“关闭前一直运行”模式中使用。编写多线程程序时,如果外部用户或系统需要向处理中的线程发出信号,告诉线程在完成当前作业后优雅关闭线程,那么就要使用volatile。 假设处理中的线程有下述代码,而这段代码在一个实现Runnable接口的类中定义:
private volatile boolean shutdown = false;
public void shutdown(){
shutdown=true;
}
public void run(){
while(!shutdown){
//处理其他任务
}
}
只要没有其他线程调用shutdown()方法,处理中的线程就回继续处理任务(经常和非常有用的blockingqueue一起使用,blockingqueue接口用于分配工作)。一旦有其他线程调用shutdown()方法,处理中的线程就会发现shutdown的值变成了true。这个变化并不影响运行中的作业,不过一旦这个任务结束,处理中的线程就不会再接受其他任务,而会优雅的关闭。
同步是为了保护对象的状态和内存,而不是代码。
同步是线程间的协助机制。
获取监视器(锁)只能避免其他线程再次获取这个监视器,而不能保护这个对象。
即便对象的监视器锁定了,不同步的方法也能看到(和修改)不一致的状态。
锁定object[]不会锁定其中的单个对象。
内部类只是语法糖,因此内部类的锁对外层类无效(反之亦然)。