Java编程思想 学习小记

1. == 和 != 比较的是对象的引用 

    equels 的默认行为是比较引用,可以通过覆写来实现其他功能


2. 基本数据类型可以任何其他的基本数据类型相转换,除了布尔型。布尔型不允许和任何基本数据类型进行转换。


3. switch...case... 中,case 也是一个一个找的,不会直接找到那一个。

    一般来说,在 case 分支可以不是用{},但是当需要在某一个 case 下面定义变量时,由于 case 分支的作用域不确定,所以编译器不会让我们在此处定义。此时可以使用{},明确作用域,然后定义变量。


4. this 关键字表示对当前对象的引用。

    类有多个构造器的时候,可以使用 this 在一个构造器中调用另一个构造器。但是注意: 一个构造器中只能调用一个其他的构造器,且这条调用语句必须放在第一行。


5. 垃圾回收相关

    垃圾回收不一定会执行,只要内存够用,就不会进行垃圾回收。

    java中分配内存: 1)。 使用 new 分配; 2). 通过其他特殊的方式分配,主要发生于使用本地函数的时候

    垃圾回收器只对 1)中分配的内存进行回收,2)的方式是不管的。

    要是需要释放 2) 中分配的内存,可以覆写 finalize 方法,在其中进行释放操作。

    过程是这样的,GC在准备释放一个对象之前,会先调用其 finalize 方法。在下一次,GC动作发生时,才会真正的释放该对象。所以, finalize 是依赖于 GC的,如果 GC 不执行, finalize 也不会被调用。

     finalize 方法尽量不要使用,它“无法预料,常常是危险的,总之是多余的”。不过它可以用于 对对象终结条件的验证。这个不细说了,位于 该书的第 88 页。


6. 初始化相关

    在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,他们仍旧会在任何方法(包括构造器)被调用之前得到初始化。这句话是针对于所有静态变量或者非静态变量而言。

    对类中静态成员的初始化只有在必要的时候才会进行。在未创建类的对象并且未调用类中的静态成员之前,类中的静态成员是不会被创建的,更不要说初始化了。

    类的构造器也是一个静态方法,尽管没有显式的声明。

    对象的创建过程,假设有个名为Dog的类:

    1)。 当首次创建Dog对象,或者首次调用Dog类中的静态成员时,java解释器会查找类路径,以定位Dog.class文件;

    2)。 然后载入Dog.class文件,此时,有关静态初始化的所有动作都会进行。因此,静态初始化只在class文件首次加载的时候,执行一次。

    3)。 当用 new Dog() 创建对象的时候,在堆上为该对象分配出足够的空间。

    4)。 将分配的空间清零,这就自动实现了所有基本成员变量的自动初始化。

    5)。 执行所有出现于字段定义处的初始化动作。

    6)。 执行构造器。

    

7. 代码组织相关

   一个.java文件中,只能有一个public 类,且该类的名称必须与该文件名称相同,其他非public 类没有限制。也可以没有 public 类。可以定义与该 top-level 类平级的接口或者类,只是可见性不可以是 public。

   使用package语句指明所属的包时,该语句必须是文件中除注释之外的第一句程序代码。

   java可运行程序是一组可以打包并压缩为一个java文档文件(JAR)的.class文件。

   java包名全部使用小写字母。

   除了内部类之外,其他类的访问权限都不可以是private或者protected,就是说他们只有 public 和 包访问 权限两种选择。如果不希望其他人使用一个类,可以把这个类的造器都定义成private的。

   相同目录下所有没有明确package声明的java文件,都被视作是该目录下默认包的一部分,即他们同属于默认包。默认包也是依赖于目录的


8. 复用类相关

   当创建了一个子类的对象时,该对象包含了一个父类的对象。这个对象与你用父类直接创建的对象是一样的。二者区别在于,后者来自于外部,前者被包装在子类对象内部。

   组合通常用于想在新类中使用现有类的功能而非它的接口这种情况。继承表示你在使用一个通用类,并为了某种需要而将其特殊化。组合表达了“has-a”的关系,继承表达了“is-a”的关系。

   继承的一般规则是:被继承的类中域尽量都是private的,方法尽量都是public的。

   应该慎用继承。如果新类需要向上转型,继承是必要的,如果不需要,则最好考虑其他的方法。

   根据惯例,既是static又是final的量,名称要用大写。一般常量都应该定义成 static final 的。

   方法的参数列表中的 final类型的参数,只是表示该参数在方法中不能被改变。与传入的实参本身是否为final无关。

   对一个方法使用final关键字,可以对该方法进行锁定,使之不能被覆盖。

   对一个类使用final关键字,意味着该类不可以被继承。该类中的方法全都被隐式的指定为final,域是不是final由自己定义。

    

9. 插入一下,java中的内存分配

   内存总体分了四个部分: stack, heap, code, data

   程序中的局部变量存在 stack 中。这些具有确定的生命周期,使用完之后会立即释放;

   程序中用 new 创建出来的对象存在 heap 中,这块内存的释放由垃圾回收机制实现。

   类中的方法存在 code 中,只有在调用的时候,才会构建该方法所需的运行环境,包括参数,局部变量等。当然,这些局部变量也是存在栈中的。

   static 变量存在 data 中。字符串池是存在栈中的。


10. 多态相关

   java中除了 static 和 final 方法之外(private 方法属于 final 方法),所有方法都是通过动态绑定实现多态的。

   对于子类向上转型为父类,可以这样理解:

   1)。 子类继承了父类,也就是说子类中有父类的东西,也有子类自己特有的东西。只有父类中允许继承的东西,才会传给子类。所以下面讨论的几条,都是针对可以从父类中继承的成员来讲的。

   2)。 对于域来讲,即使子类中的域的名称,类型与父类中完全相同,也是属于子类自己的东西。域不可以被继承(157页)。

   3)。 对于普通方法来讲,如果子类覆写了某个方法,那么在子类中覆写的这个方法是属于父类的,虽然调用时走的是子类中的实现。

   4)。 静态方法是与类绑定在一起的。如果一个子类对象,向上转型为其父类对象,那么此时这个对象被认为是属于父类。所以如果此时子类与父类中有签名完全相同的静态方法,调用时走的会是父类里面的实现。

   5)。向上转型之后,会使用子类还是父类里面的实现,全看这个成员是子类新加的,还是属于从父类继承的。子类中只有覆写的方法是还属于父类的,其他的东西都属于自己的。向上转型之后,自己的东西都不可用了,只能使用从父类里面继承的东西。

   编写 构造器的准则:尽可能简单,避免调用其他方法,尤其是可以被继承的方法(164页有反例)。

   重载:两个方法名相同,参数列表必定不同,返回值和方法修饰符可以不同。

   方法签名:包括方法名和参数列表,不包括返回值


12. 关于协变返回类型(164页)

   在 java1.4及以前,子类要想覆写父类中的方法,必须具有完全相同的方法签名,返回值也必须完全一样。

   在 java1.5之后,放宽了这个限制。只要子类方法和父类方法具有相同的签名,子类方法的返回值是父类方法的返回值的子类的对象,就可以了。


13. 抽象类和接口相关

   抽象方法:只有声明,没有实现。

   包含抽象方法的类叫做抽象类。包含的意思是有即可,不要求是”只包含“。甚至我们可以定义不包含抽象方法的抽象类

   抽象类是不能够实例化的,这似乎是抽象类更重要的一个特性。

   接口:其内部的所有方法,都是只有方法声明,没有实现。

   接口中的域都是 public static final 的,接口中的方法都是 public 的。

   接口的意义有两个: 1)。 多重继承; 2)。 不能实例化。

   编程过程中,如果知道某事物应该成为一个基类,那么首先应该考虑接口。但是接口也不能滥用。当你确定接口更好时,大胆使用它。但是一般情况下,你应该优先使用类。要是用接口,你比需充分了解它,否则,尽量不要使用。

   接口可以使用 extends 关键字来继承接口,且 extends 关键子允许继承一个接口同时继承多个接口。这与类的继承不同。

   在类中嵌套的接口,还可以具有 public 权限或者包权限。但是,接口只能被嵌套在 top-level 类中,内部类里面不可以嵌套了。

   接口彼此之间也可以嵌套。当实现一个接口时,并不需要实现嵌套在其内部的任何接口。而且,private 接口不能在定义它的类之外被实现。

   类可以被嵌套在 top-level 类和内部类中,并且可见性都可以是 public 的。


14. 内部类相关

   内部类可以直接访问其所有外围类的所有成员(包括静态成员),因为内部类对象拥有一个指向其外围类对象的引用。

   构建内部类对象时,需要一个指向其外部类对象的引用。编译器找不到这个对象就会报错。因此,在拥有外部类对象之前是不可能创建内部类对象的。必须使用外部类对象来创建内部类对象。因为这个内部类对象会暗暗地链接到创建它的外部类对象上。有一种情况例外,那就是你创建的内部类对象是嵌套类(静态内部类)的对象,那他就不需要外部类对象的引用。

   创建内部类对象可以使用 .new,比如 outer.new Inner() .

   要在匿名内部类中使用一个在其外部定义的变量时,这个变量必须是 final 的。

   嵌套类:static 的内部类。1. 创建嵌套类的对象不需要外部类对象的引用; 2. 在嵌套类内部,只能使用外部类中的静态成员。

   普通内部类不能拥有 static 的字段和方法,也不能拥有嵌套类;但是嵌套类这些都可以有。(测试了一下,普通内部类可以有 static final 的字段why??

   接口内部也可以定义类,这个类是否实现该接口都无所谓。(书202页)

   内部类的继承和覆盖,见书212页。、

   匿名内部类可以将解决特定问题的代码隔离,聚拢于一点,但是可读性不强,应该谨慎使用。


15. 持有对象相关

   迭代器 Iterator 是一个对象,它的工作是遍历并选择序列中的对象,而不用关心具体的序列类型。迭代器是一个轻量级对象,就是创建它的代价小。ListIterator 只能用于对各种 list 类的访问,它的功能比普通的 Iterator 更加强大。

   尽管已经有了 java.util.Stack 类,但是 LinkedList 可以产生更好的 Stack。

   容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换。这个过程是自动的,我们不需要手动去做。

   新程序中不应该使用过时的 Vector, Hashtable 和 Stack。

   java 容器简图可见书 246 页。


16. 异常处理相关

   当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。

   可以使用 java.util.logging 工具将输出记录到日志中。(详见书 253 页)

   重抛异常会把异常抛给上一级环境中的异常处理程序处理,同一个 try 块的后续 catch 子句将被忽略。重抛之后,异常中的所有信息都得以保持。printStackTrace 方法返回的是原异常抛出点的调用栈信息。如果要更新这个信息,可以调用fillInStackTrace 方法,这将返回一个 Throwable 对象,该对象是通过将当前调用栈信息填入原来那个异常信息而建立的。

   异常链的信息参见 书 260 页。较简单,虽然叙述复杂。

   异常的限制(运行时异常不需要人为抛出,考虑下面列的情况时,暂不考虑):

   子类继承父类,假设父类的某一构造器带有异常列表,那么如果子类的构造需要调用父类的这个构造器(不论是显式调用还是隐式调用),它的异常列表里面至少要包括父类构造器异常列表中的异常。

   子类继承父类,假设父类的某一方法带有异常列表,那么如果子类覆写了这个方法(假设可以覆写),子类中的覆写方法的异常列表中的异常必须是父类中方法的异常列表的子集(包括没有异常列表的情况)。否则一旦子类向上转型,某些异常可能得不到处理。

   某接口中有一个方法具有异常列表,那么实现该接口中的该方法时的异常列表规则和子类覆写父类的普通方法相同。同样是向上转型考虑。

   如果一个子类继承了一个父类并实现了一个接口,这个父类和接口中存在一个签名相同的方法,且这两处的方法的异常列表不一样,此时,子类应该覆写这个方法,覆写的方法的异常列表应该是 父类中方法的异常列表和接口中方法的异常列表的交集的子集。同样是向上转型考虑。

   在创建需要清理的对象之后,立即进入一个 try-finally 语句块。

   异常处理的一个重要原则是:只有在你知道如何处理的情况下才捕获异常。


17. 字符串相关

   String 对象是不可变的。String 类中每一个看起来是修改了 String 值的方法,实际上都是创建了一个全新的 String 对象,以包含修改后的字符串内容。而最初的字符串对象丝毫未动。String 对象具有只读 特性。

   想看java代码是如何实现的,可以用JDK自带的工具 javap 来反编译代码。命令是 : javap -c java文件名(不包含 .java). -c 表示将生成 JVM 字节码。

   字符串的拼接与修改,如果是较为简单的情况,可以用 “+” 来实现,这个编译器自动使用 StringBuilder 做了优化,但是还是没有直接使用 StringBuilder 来的高效。所以当情况较为复杂时,应该使用 StrngBuilder ,并且使用时最好每一个拼接部分都用一个 append,出现重载的 "+" 的话,效率就又会降低些。StringBuffer 是线程安全的,因此开销也会大些。

   如果某个对象出现在字符串表达式中(涉及 “+” 和字符串对象的表达式),该对象的 toString 方法就会被自动调用,以生成表示该对象的 String 。这个应该注意,以避免发生无意识的递归现象。

   String 类的 Trim() 方法,是删除字符串两端的空白字符,中间的不管。

   在 java 中,所有新的格式化功能都由 java.util.Formatter 类处理。这个类很强大,可以控制字符串的输出,对齐等。具体见 书290页 。

   正则表达式 书295页 。


18. 类型信息相关

   RTTI:Run-Time Type Identification,运行时类型识别。

   为了使用类而做的准备工作分为三步: 1. 加载,查找字节码,此时会创建出来一个 Class 对象; 2. 链接,验证类中的字节码,为静态域分配存储空间; 3. 初始化第二步中分配的空间,执行静态初始化器和静态初始化块。

   类中 static final 的域,如果是编译期常量(直接赋初值了),值经过第二步后就可用了,如果不是编译期常量,必须做完第三步才可用。类中 static 但是不是 final 的域,必须等到三步都做完了才可以使用。

   Class 对象:每个类都有一个 Class 对象,它包含了与类有关的信息。每当编写并且编译了一个新类,就会产生一个 Class 对象,该对象被保存在一个与该类同名的 .class 文件里。经过上面所说的三步准备工作之后,这个 Class 对象被载入内存,之后它就被用来创建这个类的所有对象。  

   要获取一个 Class 对象的引用,有两种方法:1. 调用 Class 类的静态方法, Class.forName(),参数传入类的全限定名(包括包名)。这个方法当发现这个 Class 对象尚未被加载时,会自动加载进来。所以它会抛出异常。 2. 使用类字面常量。比如一个 Toy 类,它的类字面常量就是 Toy.class 。它不会自动加载,但是更简单,更安全(因为它在编译时就会受到检查),也更高效。

   Class 对象可以使用 newInstance 方法来创建对象,但是这个方法的使用有一个前提,就是这个 Class 对象对应的类必须带有默认构造器。

   Class 对象也可以使用泛型,详见 书320页。

     

19. 数组相关

   数组的效率最高。

   数组的 length 字段只表示该数组可以容纳多少元素。length 是数组的大小,而不是实际保存的元素的个数。

   新生成的数组,其元素会被初始化。对象引用初始化为 null, 基本数据类型初始化为默认值。所以可以根据 null 的个数,来判断数组中实际保存的元素的个数(对基本数据类型不适用)。

   java 标准类库提供有 static 方法 System.arraycopy(),用它复制数组比用 for 循环复制要快得多。

   Arrays 类提供了重载后的 equals 方法,用来比较两个数组。两个数组相等的条件是:元素个数相同,且对应位置的元素也相等。

   还有数组的填充,排序,排好序之后的快速查找等,具体看书吧。


20. 容器深入研究相关

   Set 不保存重复元素,加入 set 的元素必须覆写 equals 方法以确保对象的唯一性。set 本身不维护元素的次序,TreeSet(根据元素实现的 Comparable 接口) 和 LinkedHashSet(按插入顺序) 维护。HashSet 和 LinkedHashSet 都具有很快的查找速度。一般情况下,HashSet 应该优先选择。

   对于良好的编程风格而言,在覆写 equals 方法时,总是同时覆写 hashCode 方法。

   SortedSet 和 SortedMap 是排序接口,目前实现了这两个接口的(即能排序的容器)分别是 TreeSet 和 TreeMap。

   如果没有其他限制,在对 Map 的使用上我们应该首选 HashMap,它对速度进行了优化。

   LinkedHashMap 只比 HashMap 慢一点,而在迭代访问时反而更快,因为它使用链表维护内部次序。可以在构造器中设定它,使它采用基于访问的 最近最少使用(LRU) 算法,于是没有被访问过的(可被看作需要删除的)元素就会出现在队列的前面。(store 中图标的一级缓存就是这个算法)

   如果要使用自定义的类作为 HashMap 的键,必须同时重载 hashCode() 和 equals() 方法。重载 hashCode() 是因为 HashMap 默认使用 hashCode() 方法产生散列码,重载 equals() 方法,是因为 HashMap 要使用 equals 方法判断一个键是否已经存在。 

   散列码:“相对唯一”的,用以代表对象的 int 值,它是 通过将该对象的某些信息进行转换而生成的。java 中一般使用对象的 hashCode 方法生成 散列码。使用 散列码 可以提高Map 中查找键的速度,从而大大优化 Map 的性能。

   Map 中使用散列码的原理:1)。 使用数组来存储键的信息。因为数组的查找速度最快,所以使用它。数组的大小(即“桶”的数量,数组的每一个元素称为一个“桶”)自己定义,但是为了散列分布均匀,通常使用质数,在计算机中,为使处理器处理速度加快,也可使用 2的整数次方(java 就是这样用的,因为对现代处理器来讲,除法与求模是最慢的操作,使用2的整数次方可以用掩码代替除法)。2)。数组中的元素是一个 list 的引用,list 用来存储对应于这一 桶的下标 的所有键值。不同的键值可以产生相同的散列码。3)。对传入的键,先通过 hashCode 获取它的散列码,然后经过某种运算计算出它的 桶的下标,然后将它放入那个下标对应的 list 中。如果它是这个桶上的第一个元素,那么会先创建 list 对象(之前为 null),再放进去。查询 键 的时候,也是先算出键的 桶的下标,然后找到list,然后线性查询。

   散列码不必独一无二,但是通过 hashCode() 和 equals() ,必须能够完全确定对象的身份。

   覆写 hashCode() 见书上 495 页。


21. 枚举相关

   枚举是一种类型,enum 定义出来的是一个类,枚举对象和整型数是有区别的。

   创建 enum 时,编译器会为你生成一个继承自 java.lang.Enum 的类。该类的 ordinal() 方法返回一个 int 值,这个值是每个 enum 实例在声明时的次序从0开始

   枚举中定义的值都属于这个枚举类的实例。   

   我们可以向 enum 中添加方法。注意:1)。如果打算定义自己的方法,必须在 enum 实例序列的最后添加一个分号。2)。必须在 enum 实例序列之后定义方法。

   一旦 enum 的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。我们定义自己的构造器。

   enum 可以用在 switch-case 中,但是放在 switch 中的值必须也是该 enum 的对象实例。


22. 并发相关

   阻塞:如果程序中的某个任务因为该程序控制范围之外的某些条件(通常是 I/O)而导致不能继续执行,我们就说这个任务或线程被阻塞了。

   进程:进程是运行它自己的地址空间内的自包容的程序。多任务操作系统通过周期性的将CPU从一个进程切换到另一个进程,来实现同时运行多个进程(程序)。操作系统通常会将进程互相隔离开,因此它们不会彼此干涉。与此相反的是,像 java 所使用的这种并发系统会共享诸如内存和 I/O 这样的资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。 

   与在多任务操作系统中分叉外部进程不同,线程机制 是在由执行程序表示的单一进程中创建任务。

   在单CPU机器上使用多任务的程序在任意时刻仍旧只在执行一项工作。

   java 的线程机制是 抢占式 的,这表示调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每一个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。

   一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务。你的程序使得每个人无都好像有其自己的CPU一样。其底层机制是切分CPU时间。

   Runnable 对象不会产生任何内在的线程能力,要实现线程行为,必须显式的将一个任务附着到线程上。

   Thread 的 start() 方法为该线程执行必需的初始化操作,然后在新线程中启动该任务。

   每个 Thread 都"注册"了它自己,因此确实有一个对它的引用,而且在它的任务退出其 run() 并死亡之前,垃圾回收器无法清除它。 

   java SE5 的 java.util.concurrent 包中的执行器(Executor)将为你管理 Thread 对象。Executor 在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor 允许你管理异步任务的执行,而无须显式地管理线程的生命周期。Executor 在 java SE5/6 中是启动任务的优选方法。各种 ThreadPool 对象都实现了Executor 接口,他们都是Executor 对象,可以通过 Executors.new... 来创建,返回一个统一的 ExecutorService 对象(也实现了 Executor 接口)。

   在任何线程池中,现有线程在可能的情况下,都会被自动复用。

   SingleThreadExecutor 只创建一个线程。如果向它提交了多个任务,这些任务将排队,每个任务都会在下一个任务开始之前运行结束,这些任务都是用相同的线程。因此,SingleThreadExecutor 会序列化所有提交给它的任务,并会维护自己的(隐藏)的悬挂任务队列。

   Runnable 是执行工作的独立任务,不返回任何值。如果你希望任务在完成时能返回一个值,那么可以实现 Callable 接口。Callable 是一种具有类型参数的泛型,它有一个 call() 方法,相当于 Runnable 中的 Run() 方法。Callable 的泛型参数表示的就是这个 call() 方法的返回值的类型。要执行 Callable 对象,应该是用 ExecutorService.submit() 方法。这个方法返回一个 Future 对象。Future 对象也是具有类型参数的泛型,它的泛型参数与 Callable 的泛型参数一致,因为它保存的值就是 Callable 对象的 call() 方法的返回值。通过 Futrue 对象的get()方法可以将这个返回值拿出来。get() 方法获取返回值时,如果 Future 尚未完成,它会阻塞,直到 Future 完成,再返回对应的值。使用 get() 之前也可以先调用 Future 的 isDone() 方法查询 Future 是否已经完成。

   尽管CPU处理现有线程集的顺序是不确定的,但是调度器倾向于让优先级最高的线程先执行。这并不意味着优先级较低的线程将得不到执行(也就是说,优先级不会导致死锁)。优先级较低的线程只是执行的频率较低。

   当调用 yield() 方法时,相当于给了线程调度机制一个暗示:你的工作已经做的差不多了,可以让别的线程使用CPU了(这只是一个暗示,没有任何机制保证它将会被采纳)。其实也是在建议具有相同优先级的其他线程可以运行。但是,大体上,对于任何重要的控制或者调整应用时,都不能依赖 yield() 方法。

   后台线程(daemon线程):指在程序运行的时候,在后台提供一种通用服务的线程。并且这中线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死程序中所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止,不管有没有后台线程在运行。

   如果一个线程是后台线程,那么它创建的任何线程都将被自动设置成后台线程。可以通过 isDaemon() 方法判断一个线程是否为后台线程。

   可以使用 ThreadFactory(一个接口)来批量的产生具有同一种属性的线程。 

   后台线程的 run 方法里的 finally 块中的语句有可能不被执行

   当一个线程在该线程上调用 interrupt() 方法时,该线程被中断,同时将给该线程设定一个标志,表明该线程已经被中断。然而,中断异常 被捕获时,将清理这个标志。所以在catch 子句中,在异常被捕获时这个标志总是为假。

   捕获线程执行过程中可能出现的异常(主要是 运行中异常)可以使用 Thread.UncaughtExceptionHandler 接口。具体使用方法见书上 673 页。

   java 提供了关键字 synchronized 来防止资源冲突。   当任务要执行被synchronized 关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

   可以将方法标记为 synchronized 的。每个访问临界共享资源的方法都必须同步。如果一个类中含有不止一个synchronized 方法,此时锁是对应于对象的。即 当有任务在执行其中的一个 synchronized 方法时,其他任务不仅不可以执行这个 synchronized 方法,也不可以执行其他的synchronized 方法。除非正在执行的任务释放锁。

   在使用并发的时候,将域设置为 private 是非常重要的,否则 synchronized 关键字就不能防止其他任务直接访问域,就会有问题。

   对于代码锁,还可以使用 java.util.concurrent.locks 类中显式的互斥机制。Lock 对象必须显式地被创建,锁定和释放。具体示例见 678 页。

   原子操作:不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕。

   原子性可以应用于除了 long 和 double 之外的所有基本类型之上的“简单”操作(赋值和返回操作)。

   可视性:一个任务做的修改,对于其他任务来讲是否可见,就是其他任务是否知道你做了修改和修改的结果。

   java SE5 引入了诸如 AtomicInteger, AutomicLong,AutomicReference 等特殊的原子性变量类,具体见 书 684 页。

   有时,你只是希望防止多个线程访问方法内部的部分代码而不是整个方法,通过这种方式分离出来的代码段被称为“临界区”。它也使用synchronized 关键字建立。此时,synchronized 被用来指定某个对象,此对象的锁被用来对花括号内部的代码进行同步控制。这也被称为“同步控制块”,在进入这段代码之前,必须得到 syncObject 对象的锁。

   通过使用同步代码块,而不是对整个方法进行同步控制,可以使多个任务访问对象的时间性能得到显著提高。

   synchronized 关键字不属于方法特征签名的组成部分,所以可以在覆写方法的时候加上去。

   synchronized必须指定一个在其上进行同步的对象。最合理的方式是,使用其方法正在被调用的当前对象,即 synchronized(this). 在这种方式中,如果获得了 synchronized块 上的锁,那么该对象其他的同步方法和临界区就不能被调用了。因此,如果在 this 上同步,临界区的效果就会直接缩小在同步范围内。

   如果要在另一个对象上同步,必须确保所有相关的任务都是在同一个对象上同步的。

   线程本地存储 是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。它可以根除对变量的共享。可以有 java.lang.ThreadLocal 类来实现。具体见书 690 页。

   线程四种状态

   新建(new):当线程被创建时,它只会短暂的处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或者阻塞状态。

   就绪(runnable):此时,只要调度器把时间片分给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器把时间片给线程,它就可以运行。这不同于死亡和阻塞状态。

   阻塞(blocked):线程能够运行,但是有某个条件阻止它运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何的CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。

   死亡(dead):处于死亡或者终止状态的线程将不再是可调度的。并且再也不会得到CPU时间,它的任务已经结束,或不再是可运行的。任务死亡的通常方式是从 run() 方法返回,但是任务的线程可以被中断,你需要注意这一点。

   进入阻塞状态:1)。调用 sleep 使线程休眠;2)。调用 wait() 使线程挂起,直到被唤醒;3)。任务在等待某个输入/输出完成;4)。任务试图在某个对象上调用其同步控制方法,但是对象锁不可用。

   线程中断,讲了很多,详见 书695页。


23. Java I/O 相关

   File 类这个名字有一定的误导性。该类既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。FilePath 这个名字似乎更好。

   File 类默认的 toString() 方法返回的是该 File 的绝对路径。

   编程语言的 I/O 类库常使用 “流” 这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流” 屏蔽了实际的 I/O 设备中处理数据的细节。  

   Reader 和 Writer 提供兼容 Unicode 与 面向字符 的 I/O 功能。应该尽量使用他们,不得已的时候再使用 面向字节 的类库( InputStream 和 OutputStream ),此时可优先考虑DataInputStream 和 DataOutputStream。

   RandomAccessFile 适用于由大小已知的记录组成的文件,所以我们可以使用 seek() 方法将记录从一处转移到另一处,然后读取或者修改记录。这个类是独立的类,它拥有和别的 I/O 类型本质不同的行为,我们可以在一个文件内向前和向后移动。

   DataInputStream 和 DataOutputStream 可以按照格式(如 double, byte, UTF 等)写入和读出数据。当我们使用 DataOutputStream 写字符串并且让 DataInputStream 能够恢复它的唯一可靠做法就是使用 UTF-8 编码,可以使用 writeUTF 和 readUTF 来实现。使用时,写的顺序和读的顺序必须一致

   管道流(PipedInputStream, PipedOutputStream, PipedReader, PipedWriter) 用于任务之间的通信。

   标准I/O 包括 标准的输入,输出,重定向等,见 书548页。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值