[关闭]
@lasdtc 2014-01-08T13:07:44.000000Z 字数 13236 阅读 1143

Java Note


1. Java简介

  1. Java是一种语言,更是一种程序设计平台,有庞大的库和可移植的执行环境。
  2. 特性
    • 简单:无头文件,指针,虚基类,结构,联合,操作符重载。
    • 面向对象
    • 网络技能:可处理TCP/IP协议
    • 健壮性:多方面可靠。Java指针模型可消除重写内存和破坏数据的可能性。
    • 安全性:防病毒,放篡改。程序运行时加以控制并制止恶意破坏。
    • 体系结构中立:编译器生成一个体系结构中立目标文件,只要有java运行时系统,就可以在许多处理器上运行。
    • 可移植性:无依赖具体实现的地方。
    • 解释型:java解释器执行字节码。
    • 高性能:字节码可翻译成运行该程序的特定CPU的机器码。
    • 多线程
    • 动态性
  3. Applet——在网页中运行的java程序。
  4. 术语
    • JDK——Java Develop Kit
    • JRE——Java Runtime Environment
    • SE——Standard Edition,用于桌面或简单服务器应用的java平台
    • EE——Enterprise Edition,用于复杂的服务器应用的java平台
    • ME——Micro Edition,用于微型手机和其它小型设备的Java平台

2. Java基本程序设计结构

  1. 数据类型(强类型语言)
    • Int 4字节
      Short 2字节
      Long 8字节
      Byte 1字节
      Float 4字节 有效位数6~7位
      Double 8字节 有效位数15位
      Char 2字节
    • 长整型数的后缀为L(100000000000000000L);十六进制数值有前缀0x(0x42FD);八进制有一个前缀0(070);float类型数值有后缀F(3.402F),double类型数值有后缀D(3.235823324D),没有后缀的小数默认为double类型。
    • Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY和,Double..NaN分别表示正无穷大,负无穷大和非数字(Float中也有这样的常量),这些常量不能与数值进行(==)比较,但有形如Double.isNaN(x)的方法。
    • 浮点数值不适用于禁止出现舍入误差的金融计算中。主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确的表示分数1/10(类似于十进制无法精确表示1/3)。如需数值计算中不含有任何舍入误差,应使用BigDecimal类。
    • Java中,char类型用UTF -16编码描述一个代码单元(不建议在程序中使用char)。Unicode编码单元可以表示为十六进制值,其范围从\u0000到\uffff。一个汉字由一个编码单元表示。
    • 代码点(code point)是指与一个编码表中的某个字符对应的代码值。例如U+0041就是字母A的代码点。Unicode的代码点可以分为17个代码级别(code plane)。第一个代码级别称为基本的多语言级别(basic multilingual plane),代码点从U+0000到U+FFFF,其中包括经典的Unicode代码(其中还有2048个点是空闲的,称为替代区域)。其余16个级别代码点从U+10000到U+10FFFF,其中包括了一些辅助字符。在BMP中,每个字符用16位表示,称为代码单元(code unit)。而辅助字符用连续的2个代码单元表示。U+D800~U+DBFF用于第一个代码单元,U+DC00~U+DFFF用于第二个代码单元,这些代码点都在BMP的替代区域。两个代码单元通过某种算法可以转换成一个代码点,例如U+D835和U+DD6B可编码成U+1D56B。
    • C++中,整型值和布尔值可以相互转换,但在JAVA中不行。
  2. 变量
    • JAVA中变量名长度无限制。
    • 常量用final声明,常量名使用大写,一旦赋值,不能更改。使用关键字static final设置一个类常量。
  3. 运算符
    • 由于不同处理器可能使用不同位数的寄存器来存储double值,为了保证可移植性,可在类前面使用strictfp标记,表示使用严格的浮点计算,将任何结果截断为64位,可能导致指数溢出。
    • 三元表达式x
    • 位运算符:&,|,^,~ 分别是与,或,异或,非操作,>>,<<分别表示右移和左移,>>>运算符将使用0填充高位,>>用符号位填充高位。没有<<<运算符。以为运算符右侧的参数会进行模32的运算(左边如果是long则模64)。例如1<<35与1<<3或8是相同的。
    • 在Math类中包含了各种数学函数(静态方法),如sqrt(x)开平方根,pow(x,a)幂运算,sin(),cos()等三角函数,exp(),log()指数函数和对数函数,round(x)四舍五入运算,还有常量PI -π和E -e。[StrictMath类确保所有平台得到相同结果,同(a)]
  4. 枚举类型
    • enum Size {S,M,L}; Size s = Size.M;
  5. 字符串
    • String e="Hello"; String s=e.substring(1,4); // s is "ell"
    • JAVA中字符串是不可变的,即实例不可修改,一旦更改则变为了另一个实例。与之相对的,C++可以修改字符串中的单个字符。
      • 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。
      • 如果字符串是可变的,那么会引起很严重的安全问题。黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
      • 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。
      • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键。
    • String可以检索char(代码单元),也可以检索code point代码点。(一个char是一个代码单元)
    • 如需用许多小段的字符串构造一个新字符串,应使用字符串构造器StringBuilder。其前身为StringBuffer,StringBuffer的效率略低,但允许多线程执行添加或删除字符的操作。
  6. 输入输出
    • 在java SE 6中可用Console类读取密码。Console类只能一次读取一行。
    • 可用System.out.printf像C一样执行格式化输出。另外,String.format方法可以格式化字符串。可以使用%(index)$指定要被格式化的参数索引,%<表示使用前面格式说明中的参数将被再次使用。一下两语句输出结果相同:①System.out.printf(“%1$s %2$tB %2$te %2$tY” , “Due Date” , new Date());System.out.printf(“%$s %$tB %<te %<tY” , “Due Date” , new Date());
    • Scanner和PrintWriter可分别用来输入和输出文件。System.getProperty(“user.dir”);获得程序当前路径。
  7. 控制流程
    • switch语句中case标签必须是整数或者枚举常量。
    • 带标签的break语句可以跳出多重嵌套的循环语句。标签必须放在希望跳出的最外层循环之前,必须紧跟一个冒号。
  8. 大数值
    • BigInteger和BigDecimal可以分别用于实现任意精度的整数和浮点数的运算。
  9. 数组
    • 数组可用Array.toString(a)打印,二维数组用Array.deepToString。拷贝数组用Array.copyOf方法。排序用Array.sort方法。另外比较实用的方法有binarySearch,fill,equals。
    • 数组长度允许为0,但长度为0的数组与null不等同。
    • main方法接收的String args[]参数,是命令行参数。例如,用命令java Message -g cruel world运行一个程序,args数组将包含args[0]:” -g”,args[1]:”cruel”,args[2]:”world”。
    • Java的数组变量相当于C++中的数组指针。

3. OOP

    • 类构造对象的过程称为类的实例化。对象中数据称为实例域。
    • 类之间的关系可分为:依赖(“use -a”),聚合(“has -a”),继承(“is -a”)。
    • 在java中,任何对象变量的值都是对存储在另一个地方的一个对象的引用。可将Java的对象变量看做C++的对象指针。
    • Java中很多人用访问器get()和更改器set()方法,而不是简单提供一个公有数据域public。好处:①可以改变内部实现,除该类的方法外,不会影响其他代码。例如String firstName; String lastName; getName方法可返回firstName+” ”+lastName;②更改器可以执行错误检查,然而直接对域进行赋值将不会进行这些处理。例如整数类型的age,不能被置值为 -1000 。就算此时数据不需要做限制,不能保证以后的版本中数据没有限制。
    • 不要编写返回引用可变对象的访问器方法。(可以返回int,Double这种传值的,不要返回Date这种传引用的对象,可利用clone方法返回对象的副本)
    • final实例域仅意味着相应变量中的对象引用在对象构造后不能被改变,不代表该对象是个常量,因为它不一定被初始化了。
    • this代表隐式参数,即调用该方法的对象本身。This也可用于调用另一个构造器this(...)。
  1. 静态类/方法
    • 静态域、静态方法之所以这么叫,并且用‘static’标记,只是沿用了C++的叫法,并无实际意义。叫类域或许更为准确。
    • 静态方法不能操作对象,也就是说不能访问类中实例域,但可访问静态域。
    • String是不变类(里面数据是final实例域),System.out是类System中的一个静态常量(类常量)。out之所以可以通过setOut方法改变时因为这是一个本地方法,不是用Java语言实现的,绕过来Java语言的存取控制机制。
    • Main方法常用于单元测试。
    • Java语言所有方法参数只能值调用。
    • 如果想编写一个修改数值参数值的方法,就需要使用在org.omg.CORBA包中定义的持有者(holder)类型。
  2. 对象
    • 重载(overloading):多个方法有相同名字,不同参数;重写(override):一个方法覆盖另一个同名方法。
    • 如果没有初始化类中的域,域将会被初始化为默认值。(0,false,null)
    • 仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。比如类Student只有构造器public Student(int, String),就不能使用new Student()。
    • Java中三种初始化域的方法:①在声明中赋值;②初始化块中赋值;③在构造器中设置值。(编译器按序处理)
    • Java中的包机制类似于C++中的namespace和using指令。Java中的import与C++中的#include没有共同之处。C++编译器无法查看除正在编译的文件和头文件中包含的文件以外的其它文件,所以要用到外部的东西必须用#include声明。而Java编译器可以查看任何文件内部,即使不import某个包,也可以通过显式地给出包名来调用其它文件中的类。
    • import语句还可导入静态方法和静态域。两个实际应用:①算数函数,不用Math.pow()而是pow()。②笨重的常量,不用Calendar.MONDAY而是MONDAY。
    • 一个类或其中的变量或方法,若没有被指定为public或private,这个部分(类、方法或变量)可以被同一个包中的所以方法访问。
    • JDK类加载器禁止加载用户自定义的、包名以“java.”开头的类。
    • javac编译器不检查目录结构,写错package能编译但不能运行。因为虚拟机找不到类文件
  3. 类路径(CLASS PATH)
    • 类路径下目录中的类可被多个程序共享。
    • 在UNIX环境中,类路径不同项目之间用冒号分隔,而在windows中用分号分隔。在两个系统中句点都表示当前目录。
    • javac编译器会在当前目录中查找文件,但Java虚拟机仅在类路径中查找。如果设置了类路径却没包含句点,则程序可通过编译却无法运行。
    • 编译器查找类时,会在当前文件所在路径以及import了的路径中查找类,若有多个匹配类名的类也会报错。另外,编译器会检查源文件是否比类文件新,新的话会重新编译源文件。
    • 虚拟机查找类文件会先查看jre/lib和jre/lib/ext目录下的jar包,然后再查看类路径。
  4. 文档注释
    • 注释中可加入标记@author@param等等。
    • 注释中可使用HTML修饰符,甚至用插入图片。
    • 要对包进行注释,可创建package.html文件填入相关信息,也可创建package -info.java文件。
    • 要对包中所有类提供概要性注释,可创建overview.html文件。
  5. 多态(polymorphism), 一个对象变量可以引用多种实际类型的现象。
    • 如一个Employee对象既可以引用Employee类对象,也可以引用Employee类的任何一个子类的对象。
  6. 动态绑定(dynamic binding), 一个对象在运行时能自动地选择调用哪个方法的现象。
    • 如一个类中有public方法f(int),f(String),其超类也有方法f(String)。调用方法时依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
    • 重载方法返回类型可以定义为原返回类型的子类。
    • 由于实现了动态绑定,每次调用方法都要进行搜索,增大了时间开销,因此虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名(即方法名和参数)和实际调用的方法。
    • private。static和final方法是静态绑定,编译器可以准确知道该调用哪种方法。
  7. 继承并不仅限于一个层次,但一个类只能有一个超类。要实现多继承得用接口。
  8. 出现超类对象的任何地方都可以用子类对象置换。反之不行。
  9. 阻止继承:final类和方法。
  10. 在进行强制类型转换时最好先检查被转换对象是不是该类型的实例if(staff[1] instanceof Manager)
  11. 抽象类
    • 抽象类不能被实例化。但是,可以定义一个抽象类的对象变量,其引用了非抽象子类的对象。Person p = new Student(“An Lei”, 18);
    • 类即使不含抽象方法也能被声明为抽象类。
    • 含用abstract标记的方法的类本身必须被声明为抽象的。但抽象类也可以包含具体数据和方法。
    • 抽象类的子类必须实现所有的抽象方法,否则仍需被abstract标记。
  12. 访问修饰符protected表示对本包和所有子类可见。
  13. Object,所有类的超类。
    • 基本类型(int,double等)不是对象,但所有的数组类型都拓展于Object类。
    • equals方法。如果是继承Object的equals方法,显式参数应该是equals(Object otherObject)。记得先检查引用是否相同,再检查是否为null,再比较this与otherObject是否属于同一个类(子类超类什么的,视equals的具体语义来决定使用getClass还是使用instanceof来比较),最后检查域。
    • hashCode方法,获取散列码。散列码是由对象导出的一个整型值。x.equals(y)为true则x.hashCode()==y.hashCode()。但是,如果两个对象有相同的 hashCode,但他们可以是不 equals 的
    • toString方法。一般遵循格式:类名(getClass().getName()),随后是一对方括号括起来的域值。Object的toString方法返回对象所属的类名和散列码。
  14. 对象包装器
    • 所有基本类型都有一个与之对应的类,称为包装器。如int 的包装器Integer。
    • 对象包装器类是不可变的,构造后值不能更改。另外,它还是final的,不能定义子类。
    • 编译时int自动转换为Integer这种行为称为自动打包。反之叫自动拆包。
    • 包装器之间比较用equals,不要用==。
  15. 参数数量可变的方法。如System.out.printf(String fmt, Object... args)。该方法实际接收两个参数,一个是格式字符串fmt,另一个是Object[]数组args。
  16. 反射
    • 能够分析类能力的程序称为反射。可以用反射机制:
      • 在运行中分析类能力。
      • 在运行中查看对象。
      • 实现数组的操作代码。
      • 利用Method对象,这个对象很像C++中的函数指针。
    • 程序运行时,Java runtime environment始终为所有对象维护一个被称为运行时的类型标识。这个信息保存着每个对象所属的类足迹。虚拟机利用运行时信息选择相应的方法执行。
      • 保存这些信息的类被成为Class。获得Class对象的方法:
        • Object类中的getClass()将返回一个Class类型的实例。

          类(型)中有个公有域存储对应的Class类型的实例。

        • 调用Class.forName(className)这一静态方法。className即类名。
        • 如果T是任意的Java类型,T.class代表匹配的类对象。
    • 程序在启动时,包含main方法的类被加载,然后递归加载所有需要的类。这个过程对于一个大型应用来说会很长。解决办法:首先包含main方法的类没有显式地引用其它类。然后在显示启动动画时通过调用Class.forName手工地加载其它类。
    • Class类有个newInstance()方法可以调用对应类的默认构造器以创建该类的对象。
    • 在java.lang.reflect包中有三个类Field,Method和Constructor分别用于描述类的域,方法和构造器。Class类中的getField,getMethod和getConstructor方法将分别返回以上三种类的对象的数组(包括超类的公有成员)。不过域只返回public域。而getDeclareXXX将返回全部域,方法和构造器,包括私有的和受保护的,但不包括超类的成员。
    • 反射的应用:
      • 通过Field.get,Field.set可获得并改变对象私有域的值。
      • 可利用java.lang.reflect包中的Array类Array.newInstance动态地创建数组。
      • 通过Method.invoke调用类中的相应方法。
  17. 接口

    • 接口不是类,而是对类的一组需求描述。其中只含有方法声明(和常量)。
    • 接口中所有方法属于public。
    • 接口不能实例化成对象,但其变量能引用实现了接口的类对象。
    • 要实现Object类的克隆方法(protected),必须实现cloneable接口,该接口是标记接口,并没有指定方法。
  18. 内部类

    • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。内部类的对象有一个隐式引用,它引用了实例化该内部对象的外围类对象。
    • 内部类可以同一个包中的其他类隐藏起来。
    • 当想引用一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。
    • 使用外围类引用的正规语法:OuterClass.this。
    • 在外围类的作用域之外,可以这样引用内部类:OuterClass.InnerClass。
    • 内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成名为“外围类名$内部类名.class”的常规类文件。
    • 编译器为内部类生成的构造器会将外围类做为参数传给内部类,同时在内部类中建立相应的数据域。
    • 外围类会生成类似access$0方法用于给内部类访问其私有数据。
    • 局部内部类:定义在某个代码块的类,它的作用域仅在声明这个局部类的块中,外围类中其它代码也不能访问它,但它仍能访问外围类的私有数据。
    • 局部类可以访问外围类被声明为final的局部变量(一般的内部类不行)。编译器会检测对局部变量的访问,在内部类中为每一个变量建立相应的数据域。
    • Integer对象被声明为final后其值不可变,但像数组变量等对象,被声明为final仅仅表示不可以让它引用另外一个数组,数组中元素仍可自由改变。
    • 匿名内部类:对于一个局部内部类,若只需创建这个类的一个对象,就不必命名了。通常语法格式 new SuperType( construction parameters ){ //inner class method and data }
    • 构造器名和类名必须相同,所以匿名类不能有构造器,构造参数是传给超类构造器的。而如果内部类是实现接口时,就不能有任何构造参数。
    • 静态内部类:将内部类声明为static,取消其产生的对外围类对象的引用。
  19. 代理
    • 利用代理可以在运行时创建一个实现一组给定接口的新类。也就是说,代理类Proxy能实现所有给定接口中含有的方法,并且能够在运行指定方法前执行某些动作以达到跟踪方法调用的目的。
    • 创建一个代理对象,调用Proxy类的newProxyInstance方法,该方法有三个参数:
      • 类加载器。用null表示使用默认的类加载器。
      • Class对象数组,每个元素表示需要实现的接口。
      • 调用处理器,即实现了InvocationHandler接口的对象。该接口中只有一个方法Object invoke( Object proxy, Method method, Object[] args )。无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和元素的调用参。调用处理器必须给出处理调用的方式,也就是说,所有代理对象的方法,都是先经过invoke方法后才在invoke方法中被调用的。
    • 创建的代理对象只能通过Object引用,因为代理类是在程序运行过程中创建的实现了指定接口的类,它并不是Proxy类型。
    • 一个代理类只有一个实例域——调用处理器,其它所需要的任何附加数据都必须存储在调用处理器中。
    • 代理类一点是public和final的。代理类若实现了非公有的接口,则所有非公有接口必须属于同一个包,同时,代理类也属于这个包。

12. 泛型程序设计

相当于C++中的模板。

  1. 类型参数(type parameters)。
  2. 一个泛型类(generic class)就是具有一个或多个类型变量的类。
  3. 泛型方法定义时,类型变量放在修饰符的后面,返回类型的前面。普通类中也可定义泛型方法。
  4. 可使用关键字extends将类型变量限制为实现了某些接口。
  5. 对于每一个泛型类型,都提供了一个相应的原始类型,即去掉类型参数的普通类,类型变量被替换为限定类型。

集合类层次

14. 多线程

  1. 概念
    • 进程拥有自己一整套变量;线程共享数据,更轻量级。线程之间比进程之间通信更简单高效,创建撤销线程比进程开销小。
    • Thread.sleep()是Thread的静态方法,暂停当前线程的活动。线程被阻塞,无法检测中断。在中断状态被置位时会抛出InterruptedExceptin异常。
    • Thread t = new Thread(new Runnable()); t.start;
    • 不能直接调用Thread类和Runnable对象的run方法。直接调用run方法只会执行同一个线程中的任务,不会启动新线程。应调用Thread.run()。
    • 利用中断终止线程,而不是被弃用的stop和suspend方法。一个线程要判断自己是否被中断:Thread.currentThread().isInterrupted()。一般程序在被中断时应该停止。
    • 线程状态:①New,new Thread后。②Runnable,Thread.star()后,此时线程可能正在运行也可能没有运行。③Blocked,当线程试图获取一个内部的对象锁,而该锁被其它线程持有,则该线程进入阻塞状态。④Waiting,当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。⑤Timed waiting,有几个方法(如Thread.sleep,Object.wait等)有超时参数,调用这些方法导致线程进入计时等待。⑥Terminated,线程正常退出或抛出异常。
    • 守护线程是不会影响程序终止的线程。一个程序若只剩下守护线程在跑,程序就会结束。
  2. 同步(底层知识)
    • 竞争条件(race condition),两个或两个以上的线程需要共享对同一数据的存取。
    • 两种同步方法:锁对象ReentrantLock;关键字synchronized。
    • 每个对象的内部都有一个锁,并且配有一个相关条件对象。synchronized就是使用内部锁。wait()相当于调用内部锁的await()方法,notifyAll()相当于调用内部锁的signalAll()方法。另外,synchronized也可以不调用内部锁,而调用指定对象的锁,如synchronized(object){}。
    • 非静态方法使用synchronized修饰,相当于synchronized(this)。 静态方法使用synchronized修饰,相当于synchronized(Lock.class)。
    • 监视器(不需要程序猿考虑如何加锁的情况下保证多线程安全的解决方案):监视器是只包含私有域的类;每个监视器类的对象有一个相关的锁;使用该锁对类中所有的方法进行加锁;该锁可以有任意多个相关条件。
    • 多线程易出错的原因:①多处理器能在寄存器或本地内存缓冲区中保存内存中的值。②编译器可以改变指令执行顺序以使吞吐量最大化。但是编译器嘉定内存的值仅在代码中有显式的修改指令时才改变,然而,内存中的值可以被其它线程改变。
    • Volatile关键字为实例域的同步访问提供了一种免锁机制。(但由于只是保证各个线程取出的值是相同的,而不能保证对变量的操作是原子性的,所以仍不能避免同步错误的发生。)
    • volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值——每次都会从内存中读取。而对该变量的修改,volatile并不提供原子性的保证。
    • 原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。可以理解成语句翻译成机器码之后,处理器是一次性执行完的,期间不会中断去执行其它的线程。在Java中,对除了long和double之外的基本类型的简单操作都具有原子性。简单操作就是赋值或者return。a++,a+=b都不具有原子性。
    • 在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中 的原子性。
    • Lock.tryLock()尝试获得锁,成功则返回true,否则返回false。可有超时参数,并且超时等待时可以被中断,抛InterruptedException异常。
    • ReentrantReadWriteLock类加锁时,允许对读者线程共享访问,但写者线程互斥访问。
    • Thread.stop方法会使线程终止并立即释放它持有的所有对象的锁,可能有些涉及安全性的操作进行一半就停了。该方法已弃用。停止线程应中断线程,被中断线程会在安全的时候停止。
    • Thread.suspend方法会阻塞进程。若该进程持有锁,那么该锁在该进程恢复之前是不可用的。如果调用suspend方法挂起一个线程的另一个线程在之后试图获得挂起线程的锁,那么程序死锁。所以该方法已弃用。
    • Callable接口与Runnable接口类类似,但是有返回值,只有一个方法call()。Future接口保存异步运算结果,调用get方法去获得结果。FutureTask包装器可将Callable转换成Future和Runnable。
      Java同步机制
  3. 线程安全的集合
    • 阻塞队列。生产线程向队列插入元素,消费者则取出他们进行操作。这样一次只有一个线程占有一个元素,元素从队列中被取出后其它线程无法得到它进行修改。
    • ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue。
    • 另外,任何集合类可通过同步包装器(synchronization wrapper)变成线程安全的。如:List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());结果集合的方法全被使用锁甲乙保护,提供了线程的安全访问。尽管被包装成同步的了,但在集合使用到了迭代器时,还是得把操作用锁封闭起来。
  4. 线程池
    • 一个线程池中包含许多准备运行的空闲的线程。当一个线程的run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
    • 使用线程池可以减少并发线程的数目。创建线程会降低处理器性能,如果有一个会创建许多线程的算法,应使用一个线程数“固定的”线程池以限制并发线程的总数。
    • 执行器(Executor)类有许多静态工厂方法用来构建线程池。
    • 线程池里的线程可以是不同的线程类。
  5. 同步器
    • 信号量(Semaphore)。一个信号量管理许多的许可证(permit),仅维护一个计数。acquire请求许可,release释放许可。
    • 倒计时门栓(CountDownLatch)。倒计时门栓是一次性的,一旦计数为0就被弃用了。
    • 障栅(barrier)。CyclicBarrier类实现了一个集结点(rendezvous),称为障栅。适用于大量线程运行一次计算的不同部分的情形。当一个线程完成了它那部分任务后,我们让它运行到障栅处(barrier.await())。一旦所有线程都到达障栅,障栅就撤销,线程继续运行。
    • 交换器(Exchanger)。这里有个比喻,服务员向原有空杯中不断倒水,消费者不断从原有装满水的杯中喝水。 当服务员倒满水和消费者喝完水时,两个杯子进行交换。一直这样周而复始。 这里“杯子”就是缓冲区,水是数据,“服务员”和“消费者”是两个线程。交换器的作用就是交换“服务员”和“消费者”的杯子。Exchanger必须是偶数个线程间的交换,否则会有一个线程一直拿不到数据,从而抛出java.util.concurrent.TimeoutException异常。
    • 同步队列,SynchronousQueue。该队列是个阻塞队列,其中每个 put 必须等待一个 take,反之亦然。就是说,一个线程调用put方法时,该线程就会被阻塞,直到另一个线程调用take方法把元素取走。
    • Swing中的EventQueue负责所有AWTEvent(以及其子类,SelectionEvent,PaintEvent等)的分发。在EventQueue中有一个dispatchThread,这是一个线程类,负责所有事件的分发,也就是说是一个事件一个事件处理的,如果有某一个事件处理的时间非常长的时侯,其他事件就会被堵塞在那里,从现象上看得话,就是界面会死掉。
  6. 线程与Swing
    • Swing不是线程安全的,不要试图在多个线程中操纵用户界面元素。
    • Swing与线程一起使用时的原则:①如果一个动作需要花费很长时间,在一个独立的工作器线程中做这件事不要在事件分配线程中做。②除了事件分配线程,不要在任何线程中接触Swing组件。简单理解就是不要在Listener中写耗时很长的方法,可以另起一个线程,但修改swing组件的操作得写在对event的处理中。
    • 为了满足b)中原则②,可以利用EventQueue类的invokeLater或invokeAndWait方法向事件队列添加任意的动作(修改Swing组件的动作)。因为Swing中修改组件的动作都是通过Event完成的,而所有Event都是在EventQueue管理下一个一个执行的。
    • SwingWorker类可帮助启动新线程完成耗时的命令。

15. 其它

  1. 程序配置
    • 清单文件(manifest.mf)的最后一行必须以换行符结束,否则无法读取。
    • 利用Properties类创建属性映射来存放配置信息。利用Preferences类往系统中心知识库(如Windows的注册信息表)中写入配置信息。
  2. 异常
    • Throwable分支Error和Exception。Error描述Java运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的错误。Exception分支IOException和Runtime Exception。由程序错误导致的异常属于RuntimeException,而由于像I/O这类问题导致的异常属于其他异常(IOException)。异常类层次
    • 子类中声明的已检查异常不能超过超类方法中声明的异常范围。
    • 是在方法中处理异常还是将异常throw出去,应该看该方法是否知道如何处理异常。
    • 多数情况下,与其返回null,不如抛出异常。
    • 断言机制允许在测试期间向代码中插入一些检查语句。检查某个属性是否符合要求,而代码的执行依赖于这个属性。
  3. 类实例化后称为对象。

 JVM 运行时数据区域 Run -Time Data Areas

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注