自己整理的 java 面试题 第一章 java 编程基础 (1)简述 java 开发包的种类 Javame javase javaee 。 Javame 一种已广泛的消费性产品为目的的高度优化的 java 运行环境 Javase 是针对桌面开发以及低端上午计算机解决方案而开放的版本 Javaee 是一种利用 java 平台来简化企业解决方案的开发,部署和管理相关的复杂问题的体 系。 (2)java 语言的特性有哪些 Java 语言是一个面向对象的程序设计语言。主要特性如下 A java 语言是简单的。 B java 语言是面向对象的 C java 语言是分布式的 Djava 语言是健壮的 E java 语言是安全的 F·java 语言是跨平台的 Gjava 语言是多线程的 (3)linux 环境下是如何安装 javase 环境 注意很 windows 环境大体相似 找到相应的 linux 环境版本然后配置好环境变量 (4)列出你熟悉的 java 开发工具 A jdk (java Dvelopment kit):最基本的开发工具 B Borland jbuilder 付费 历史久远 C oracle jdeveloper 对于初学者来说 比较复杂 D IBM visual age of java 非常主流的开发工具 E eclipes 是一种可以扩展的开放源码的集成开发环境 (5)用自己的语言描述 java 中的类和对象 对象是类的实例 类 面向对象编程 oop 的实质就是利用类和对象来建立抽象模型 类表示对象的类别 是创建对象的蓝本(也就说类事蓝本,不是对象自己认为) 。建立一个事 物的抽象模型本质上就是表达该事物的性质和行为。 类中定义的属性是一种可存储值得变量,该变量代表着事物具体的性质。 在项目开发中,雷一般可以通过 uml 建模来表示 对象 对象是类的实例,对象在构造是一类为蓝本,穿件对象的过程叫做实例化。对象是表示抽象 事物的具体实例。 (6)、 ,命名 java 变量 1 包名的命名 Java 包的名字都是有小写单词组成 类的命名 类的名字必须由大写字母开头,单词中的其他字母均为小写 方法的命名 方法的名字的第一个单词应为小写字母作为开头,后天单词则建议以用大写开头 常量的命名 使用大写 参数的命名 参数地命名规范和方法命名规范相同 Javadoc 注解 以/**开头 以 * /结束 (7)理解成员 实例成员隶属于实例,但不隶属于整个类 1 类成成员 对象是以类为蓝本创建的类的实例。 对象的方法定义了该对象的行为, 一般被称为实例方法。 隶属于对象的实例变量和实例方法合称为对象的实例成员。 值得注意的是, 应与类静态成员 相区别,静态成员只是隶属于类, 而不隶属于对象 2 静态成员 静态成员隶属于类, 而不属于任何该类生成的对象, 当类加载时就会初始化静态变量; 同样, 静态方法也是隶属于类的么人不是对象,静态变量和静态方法合称为静态成员。 成员名称 实例成员 实例变量 成员含义 指的是对象的实例变量和实例方法,只能通 过对象应用加以访问或者调用 实例变量只有在类的实例化时才分配字段, 即当创建类的对象的时候,系统才分配这个 字段,也叫做非静态变量 一种隶属于类实力的方法,像同类的所有对 象共享该方法的实现 指的是隶属于类的静态方法和静态变量,静 态成员可以通过类名或者对象的引用加以访 问,调用 一种载入类时就需要进行分配的变量, 静态变量隶属于类,但是不属于该类的任何 对象,所以成为静态变量或者类变量 一种隶属类但是不属于该类任何对象的方 法,叫做类方法,同样可以通过类名或者对 象引用访问,调用 实例方法 静态成员 静态变量 静态方法 (8) 在 java 中,对象是如何传递消息 在 Java 对象之间的消息传递,是通过调用相互调用实例方法来实现的,而不是静态方法 (9) 对于类的静态变量的理解 运行下面程序,请选择输出结果 Public class x{ Private static int a; Public static void main(String args[]){ Modify(a); System.out.println(a); } Public static void modify(int a){ a++; ] } 类的静态变量 a 在 modify()方法中并没有被改变,而是改变了 modify 方法的参数。所以 输出结果是 0 public class x { private static int a; public static void main(String[] args) { modify(); System.out.println(a); } private static void modify() { // TODO Auto-generated method stub a++; } } 如果改成这样输出就是为 1 (10) 类的继承建模方法 Java 中根据继承和聚合两种机制来保证根据现有的类构造新的类。聚合(聚合 简单的理解 是一个类里包含其他的类 举例 图书馆 图书馆的类里有书,目录,借书记录等类)再现有 的类构造出新的类,需要在子类的定义中使用 extends 子句,子类只能扩展一种父类,子类 将继承超类的成员。注意修饰符(区分 java 关键字) 例如: public class Test { public static void main(String[] args) { Shape shape = new Circle(); System.out.println(shape.name); shape.printType(); shape.printName(); } } class Shape { public String name = public Shape(){ "shape"; System.out.println("shape constructor"); } public void printType() { System.out.println("this is shape"); } public static void printName() { System.out.println("shape"); } } class Circle extends Shape { public String name = "circle"; public Circle() { System.out.println("circle constructor"); } public void printType() { System.out.println("this is circle"); } public static void printName() { System.out.println("circle"); } } 答案输出: shape constructor circle constructor shape this is circle shape (11) 使用 jar 命令 求职者应该熟悉掌握 j’avac java 和 jar 等工具 Javac 是 jdk 标准编译工具,常见的形式为 javac <选项><源文件> 2java Java 命令是 jdk 默认的 java 执行程序,在这里值得注意的是,在执行带包的.class 文件时, 需要使用标准的 java 包形式。 Java com.n.hello 3Jar 可以使用 jar cf test.jar.test 命令来完成压缩。在压缩包中,包含一个 test 文件夹和一个 META-INF 文件夹,其中有一个 MANIDFEST.MF 文件 (12)如何执行 main()方法 利用 java sdk 中的那条指令能够执行 test 的 main()方法? A java test B javac test C java test.java D java test.class E java test.class 求职者最容易错的误选择 d 实际上 Java 命令后面只需要 class 文件的名称就可以,不用加 入 .class 全名。如果使用 java test .class 命令,系统会认为这是一个包的结构,会查找 test.class.class 文件,会出现 找不到相应的类文件 参考答案 a 第二章 java 语言基础 Java 语言基础主要涉及一下几个方面 Java 的基本语言元素 原始数据类型 变量声明 变量的初始化 Java 源文件的结构 Main 方法 面试(1)掌握 java 标示符 请标记出下面代码中的错误。 public class x { private int m-48abc; private int m_abc; private int number=60; private static String $hk; private double !@#; private static String 我是变量="我是一个兵"; private String my@email="helloworld"; private String String; private String sizeof; private String null; private String default; private Stirng throws; public static void main(String[] args) { System.out.println(我是变量); System.out.println($hk); /*//*/ } } 标识符是一个语言的基本元素,在 java 开发语言中,标示符可以用来标示类、方法、变量和标 签。标识符是由一系列字符组成,每个字符可以是字母、数字、链接符号_或者是货币符号。 求职者注意第一个字母不能为数字,JAVA 语言是区分大小写的。 (2)掌握 java 关键字 JAVA 语言中的关键字是开发语言中预定于的保留标识符,不能在程序代码中标示类、变量、方 法等成员。Java 关键字有如下特征 A java 的关键字都是小写 B then 和 sizeof 都不是 java 关键字。熟悉 c 或者 c++,写惯 asp 的程序员一定要注意。 C 大写的 NULL 不是 JAVA 语言关键字。String 是 java 语言的一个封装类的类名,也不是关键 字。 关键字 abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for goto if 用在异常处理中,用来捕捉异常 基本数据类型之一,字符类型 类 保留关键字,没有具体含义 回到一个块的开始处 默认,例如,用在 switch 语句中,表明一个默认的分支 用在 do-while 循环结构中 基本数据类型之一,双精度浮点数类型 用在条件语句中,表明当条件不成立时的分支 枚举 表明一个类型是另一个类型的子类型,这里常见的类型有类和接口 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员 域的值不能被改变 用于处理异常情况,用来声明一个基本肯定会被执行到的语句块 基本数据类型之一,单精度浮点数类型 一种循环结构的引导词 保留关键字,没有具体含义 条件语句的引导词 含义 表明类或者成员方法具有抽象属性 用来进行程序调试 基本数据类型之一,布尔类型 提前跳出一个块 基本数据类型之一,字节类型 用在 switch 语句之中,表示其中的一个分支 implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchroniz ed this throw throws transient try void volatile 表明一个类实现了给定的接口 表明要访问指定的类或包 用来测试一个对象是否是指定类型的实例对象 基本数据类型之一,整数类型 接口 基本数据类型之一,长整数类型 用来声明一个方法是由与计算机相关的语言(如 C/C++/FORTRAN 语言)实现的 用来创建新实例对象 包 一种访问控制方式:私用模式 一种访问控制方式:保护模式 一种访问控制方式:共用模式 从成员方法中返回数据 基本数据类型之一,短整数类型 表明具有静态属性 用来声明 FP_strict(单精度或双精度浮点数)表达式遵循 IEEE 754 算术规范 表明当前对象的父类型的引用或者父类型的构造方法 分支语句结构的引导词 表明一段代码需要同步执行 指向当前实例对象的引用 抛出一个异常 声明在当前定义的成员方法中所有需要抛出的异常 声明不用序列化的成员域 尝试一个可能抛出异常的程序块 声明当前成员方法没有返回值 表明两个或者多个变量必须同步地发生变化 while 用在循环结构中 Java 语言中的保留字:null、ture 和 false Java 语言中保留但是未使用的关键字:const 和 goto (3)找出 java 关键字 A if B then C goto D while E then F sizeof 参考上面那道题 答案 acde; (4)挑选 java 关键字 A default B NULL C String (5)选择完整合理的 java 注解 请问这是一个完整且合理的 java 注解吗 /*//*/ Java 语言提供了 3 种用于程序的注解 单行注解://***到行尾 多行注解 /*…*/ 归档 javadoc 注解://**..*/ 1. 文件头注解 /** * 该类主要演示了如何使用微博 OpenAPI 来获取以下内容: *
获取某条微博的评论列表 *
... * * @author SINA * @since 2013-11-24 */ 需要注明该类文件创建时间、文件名、命名空间信息 2. 类、接口注释电放费 /** * @see {@link AdapterView.OnItemClickListener#onItemClick} */ 类和接口采用/** */,描述部分用来书写该类的作用或者相关信息。 3. 构造函数注解 /** * HTTP 同步请求。 * * @param url * @param params * * @return 同步请求后,服务器返回的字符串。 4. */ 使用/** */描述构造函数的作用 5. 域注解 6. 方法注解 (6)int 和 Integer 的区别是什么 java 提供引用类型和原始类型(或者内置类型)两种不同的类型。另外,java 还以每个原始类 型提供封装类(Wrapper)。 请求的地址 请求的参数 D throw E long F true * @param httpMethod 请求方法 原始类型 引用类型 ================= boolean Boolean char Character byte Byte short Short int Integer long Long float Float double Double 引用类型和原始类型的行为完全不一样,并且分别具有不同的语义。 1 int i=5; //原始类型 Integer j=new Integer(10);//对象引用 这两个变量都存储在局部变量表中,并且都是在 java 操作数堆栈中被操作的。原始类型和对象 引用各占堆栈的 32 位。Integer 类型对象对应的堆栈项并不是对象本身,而是一个对象引用, 对象引用是指向对象所在堆中的某个区域指针。Java 中的所有对象都要通过对象引用访问。而 当声明了一个原始类型,就为类型本身声明了存储。 (7)哪些不属于 java 原始类型 A ?abc? B ‘ x’ C 100.09F D ?hello? E false 答案 Ad 除外其他都是原始类型(八个不包含 string),而 ad 是字符串,都是 string 对象类型 (8)String 和 StringBuffer 的区别 Java 提供了两个字符串类:String 和 StringBUffer。字符串 String 类提供了数值不可以改 变的字符串,而 StringBuffer 类提供的字符串对象可以进行更改。 (9)指出 java 的原始数据类型并说出其分类 Java 编程语言为 8 个原始基本类型: boolean,char,byte,short,int,long,double,flout 逻辑类:boolean boolean truth = true; 注意:在整数类型和 boolean 之间无转换计算.有些语言(特别强调的是 C 和 C++)允 许将数字值,转换成逻辑值,这在 Java 编程语言中是不允许的;boolean 类型只允许使用 boolean 值. if(a==2)条件表达式返回的是 boolean. 文本类:char 和 String char 代表一个 16-bit 的 Unicode 字符. 必须包含用单引号(')引用的文字. '\t' 一个制表符; '\u????' 一个特殊的 Unicode 字符,????应严格使用四个 16 进制数进行替换. String-不是原始类型,而是一个类,它被用来表示字符序列.(与 C 和 C++不同 String 不 能用\0 作为结束); -具有双引号引用的文字. 注意:String s = "Good, morning \n"; //"\n"表示一 个换行 System.out.println(s); //这里打印后会换 2 行 整数类:byte,short,int,long(注意 java 中整数类型都是带符号的) 1,采用 3 种进制十,八,十六进制 2 表示十进制的 2 017 首位的 0,表示这是一个八进制数 0x1A 前 2 位的 0x,表示这是一个 16 进制数 2,具有缺省 int(整数类文字属 int 类型,除非在后面直接跟一个字母"L")."L"表示一个 long 值 浮点(float 和 double): 浮点变量可用 float 或 double 来声明.如果一个数字包括小数点或指数部分,或者在数字 后面带有字母 F 或 f,D 或 d,则该 文字为浮点. -缺省为 double -浮点文字包括小数点或下列各项之一 -E 或 e 增加指数值 -F 或 f(float) 32bits -D 或 d(double) 64bits System.out.println(123.4E+306D);//=1.234E308??? 类型自动转换 ...................char ........↓ byte→short→int→long ..... ... ↘ .......float → double 另外 long int 和 float double 有可能损失精度 System.out.println((12.0==12)); 12.0 浮点型 double 12 数值型 int int→double 自动转换 所以结果为真 返还布尔值 TRUE System.out.println(('a'==97.0)); "a"字符 char 97.0 为浮点值 double char→int→double 自动转换 a 的进制表示就是 97 97==97.0 所以结果为真 返还布尔值 TRUE (10)理解和掌握变量初始化 写 Java 代码的时候很少去关注成员变量的声明和初始化顺序, 今天借此机会抛出一些问题: 语言的设计者们为什么会这样设计?比如说很常见的一个问题:abstract(抽象)类不能用 final 进行修饰。这个问题比较好理解:因为一个类一旦被修饰成了 final,那么意味着这个 类是不能被继承的,而 abstract(抽象)类又不能被实例化。如果一个抽象类可以是 final 类型的,那么这个类又不能被继承也不能被实例化,就没有存在的意义。从语言的角度来 讲一个类既然是抽象类,那么它就是为了继承,所以给它标识为 final 是没有意义的。语言 的设计者们当然不可能让这么大的一个 bug 产生。对于开发者而言抽象类不能修饰 final 可 能就是一种约定俗成的规定, 并没有特殊意义。 我们完全可以往前想一点: 为什么这么设计? 下面我所展示的一些代码实例也同样会采用我上面的一些思考方法。有一些是一些” 契约“,并没有特别的缘由,可能用别的方法也是合理的。下面的代码会讲到初始化的一些 策略,从实际的执行结果中得出一些结论。 1. public class Test1 { 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. } } } private int a=2;//这里初始化一次, 上面的动态块中也对 a 进行了赋值, 这个时候 a=?, 为什么可以对 a 进行赋值,而不可以对 a 进行输出 public static void main(String[] args){ Test1 test1 = new Test1(); System.out.println(test1.a); { a = 1; //System.out.println(a);//这里会抛错。 看看上面的代码一,第一个问题就是这段代码能否编译通过。结果是能编译通过。这里 说明一个问题就是变量的声明和赋值是两步操作?(这句话我先保留一半,在上面 的代码中有一行代码我注释掉了, 这里会抛错, 对于这个问题我也没有想明白为什么。 ) 第一个问题解决了。那下一个问题很显然最后输出的结果是什么?答案是“2”,这里 可能会有些诧异。从直观上来讲就是说明在赋值的过程中是完全按照代码的前后顺 序进 行。 1. public class Test2 { 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. } } public static void main(String[] args){ Test2 test2 = new Test2(); System.out.println(test2.a); private final int a;//这里我并没有对 a 做初始化。 } { a = 4; 代码二”只是在“代码一”的基础上对成员变量 a 多修饰了一个 final,另外我并没有立即 初始化。第一个问题就是这段代码能不能编译通过,答案是能。在这里展示这段代码是为了 后面做铺垫,因为这段代码仍然符合上面的“契约”。 1. public class Test3 { 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. } } public static void main(String[] args){ Test3 test3 = new Test3();//注意:这里开始 new 了一个对象 System.out.println(test3.a); private static int a; } { a = 4; 代码三在代码一的的基础上对于成员变量 a 多修饰了一个 static。这里同样可以编译通过, 最后输出的结果也皆大欢喜为 4。这里要注意的是我是 new 了一个对象,而不是直接访问 静态变量。 1. public class Test3 { 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. } } public static void main(String[] args){ System.out.println(a); } private static int a; { a = 4; System.out.println(a);//这里不会报错,但是这条语句并不会执行 代码四在代码三的基础上把 new 对象给去掉了,直接输出静态变量 a。这时候就会出现非 常诧异的结果 0。对,你没有看错,结果是 0。如果有兴趣的可以在 a=4 后面打印一条,会 很清晰的发现并没有执行 a=4 那一条语句。这里先不解释,只看一下现象。 1. public class Test3 { 2. 3. 4. 5. 6. 7. 8. 9. 10. } } } private static int a; public static void main(String[] args){ System.out.println(a); static{ a = 4; //System.out.println(a);//这里会抛错。 代码五和代码四和不同在于采用了静态初始化,最后的结论很简单,结果为 4。这里有一个 问题就是如果在 a=4 之后紧接着使用 a 就会报错。也就是说定义在声明之前的静态化块只 能对声明变量进行赋值,并不能使用该变量。对于这一条规则我也不是特别理解,因为按照 常理在赋值之后进行使用是一种再正常不过的事情,在这里只有记住这样一条规则。 1. public class Test6 { 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. } public static void main(String[] args){ System.out.println(a); private static final int a; } { a = 4; 12. } 代码六是在代码一的基础上增加 static final 的修饰符。回到我们上面三段代码所问的 问题,这次的答案是“否”,也就是说在这里是不能编译通过。在这里我估计有一部分人和 我有同样的疑惑:为什么对于成员变量修饰单独修饰 final 或者 static 可以进行单独的 初始化, 而把两个修饰符合起来的时候就不行了呢?我们把这个问题要反过来问: 如果可以 这样进行初始化会产生什么问题,那么就可以知道为什么需要这样设计? 我们看代码三、代码四、代码五和代码六,这里估计会有点绕。上面也说了在代码 三中的初始化块是执行了的, 而代码四的初始化块没有执行, 代码五的静态初始化块也执行 了。所以问题归根结底就一条:静态初始化块和普通的初始化块在什么时候执行。结论就是 在初始化阶段编译器会收集类中的类变量 (区别实例变量) 的赋值动作和静态语句块中的语 句,而静态的调用并不会触发实例变量的初始化。 这里回到代码六,根据上面所得出的结论。变量 a 被修鉓成了 static final,那 么意味着有且仅有一次赋值。我们在访问 a 的同时,域中的 a=4 并未执行(根据代码四所 得出的结论),这样就违背了 final 类型有且仅有一次赋值的这样一个约定。所以{a=4;} 不管是放在声明的代码前还是声明的代码后都无法编译通过。 第八章 一般来说,把正在计算机中执行的程序叫做“进程” ,所谓的“线程”是指 “进程”中某个 单一顺序的控制流。 要求读者掌握 1 掌握 java 多线程机制 2.直到进程和线程的区别 3.掌握进程的几种状态 4.掌握使用两种方式创建进程 5.掌握线程的同步 6 掌握线程不同状态之间的转化 Q 请说明进程好线程的区别 值得注意的是进程是属操作系统的 a 一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程,使用多 线程程序的并发性高 b 进程在执行过程中拥有独立的内存单元,而多个线程共享内容,从而极大地提高了程序的 执行效率 c.线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的入口、顺序 执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中。 D. 从逻辑角度上看, 多线程的意义在于一个应用程序中可有有多个执行部分可以同时执行。 操作系统并没有将多个线程看作是多个独立的应用, 来实现程序的调度和管理, 以及资源分 配。这就是进程和线程的主要区别 E.线程是具有独立功能的程序关于某个数据集合上一次运动活动,进程是系统进行资源分 配和调度的一个独立的单位 F.线程是进程的一个实体,是 cpu 调度和分派的基本单位,他是比进程更小的独立运行的 基本单位。县城本身基本上不拥有资源,只拥有一点在运行中必不可少的资源(栈,程序计 数器,一组寄存器) ,但是它可与同属于一个进程的其他线程共享进程所拥有的全部资源 G 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 最主要的区别是 它们是不同于操作系统资源管理方式 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程 只是一个进程中的不同执行路径。 线程有自己的栈堆和局部变量, 但线程之间没有单独的地 址空间,一个线程死掉就等于整个程序死掉,所以多进程的程序要比多线程程序健壮,但是 进程切换是,消耗资源较大,效率要差一些。 但是对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 Q 一个具有生命的线程有哪些状态 创建、就绪、运行、阻塞、死亡五中状态 1 创建 实例化 Thread 对象,但没有调用 start( (方法时的状态 ThreadTest a=new ThreadTest() ;或者 Thread aa=new Thread(a) ; 虽然创建了 Thread 对象但是还是不能通过 isAlive() 2 就绪 线程有资格运行,但调度进程还没有把它选为运行线程时所处的状态 线程创建后,调用了 start 方法,线程处于运行状态,但能通过 isaliver 方法 3 运行 从就绪池中被选为但前执行的线程所处的状态 4 等待、阻塞或者睡眠 线程依然活着,但是缺少条件,一旦具备条件就转化为就绪状态 Suspend 方法和 stop 方法已经被废弃了,比较危险 5 死亡 一个线程 run 方法运行结束,那么该线程完成使命,他的栈结构将解散,也就是死 完了。但他任然是一个 Thread 对象,仍然可以被调用,这一点与其他对象一样,而且别引 用的对象也不会被垃圾回收器回收 一旦线程死去,永远不能从新启动了,也就是说不能再使用 start 方法让它运行 Q3 那个方法是正确启动新线程。 B 创建线程对象并且调用 start 方法 对于 start 方法的调用会立即返回,而线程会异步启动 run 方法 Q4 如何创建启动线程 1 继承 java。Lang。Thread 类 Calss ThreadTest extends Thread { Public static void main(String【】 args){ Thread t=new Thread() ; t.start(); t.run(“nihao”); } Public void run(String name){ System.out.println(“string in run is…”); } Public void run(){ System.out.println(“something run here!”); } } Output String int… Something run here 一旦调用 start 方法,必须给 jvm 留足时间,让它配置进程,而在 jvm 配置完成前,重载的 run ( string )方法被调用了,结果反而先输出 string in.. 然后 线程配置完成后 子啊输出 something 验证,如果在 t.start 加上 for 循环,给足充足时间,run 方法就能执行 注意:这种输出结果顺序是没有保障的!不要依赖循环耗时! Thread 类中有许多管理线程的方法,包括创建、启动和暂停,所有的操作都是从 run 方法开 始,并且在 run 方法内编写需要在独立线程内执行的代码。Run 方法可以调用其它方法,但 是执行的线程总是通过调用 run 方法 没有参数的 run 方法时自动被调用的,而带参数的 run 方法是被重载的,必须显示调用。 2 实现 java。Lang。Runnable 接口 Class Thread implments Runnable{ Public void run(){ System.out.println(“something run here”); } ..Main() { ThreadTest tt =new ThreadTest(); Thread t1=new Thread(tt); Thread t2=new Thread (tt); T1.start(); T2.start(); } } Z 这种方式把线程相关的代码和线程要执行的代码分离开来。 另一种是参数式匿名内部类, Class ThreadTest{ …Main(){ Thread t=new Thread(\new Runnable(){ Public void run(){ System.out.println(“anonymous thread”); } } ); t.start(); } } 线程的启动要调用 start 方法,只有这样才能创建新的调用栈。而直接调用 run 方法的话, 就不会创建新的调用栈,也不会创建新的线程,run 方法就与普通的方法没设么两样 Q5 选择正确的线程说法 下面哪种说法正确? A thread 类是抽象类 B Thread 类实现 Runnable C 实现 Runnable 接口的类必须定义一个 start 方法 D 实现 Runnable 的对象调用 run 方法将创建一个新的线程 E 当最后一个非守护线程结束时,程序将结束 Thread 类实现了 Runnable 接口,不是抽象类 当最后一个非后台线程结束时,程序也就终止了 Runnable 接口有一个 run 方法,不过该接口没有规定必须定义一个 start 方法 在一个 runnable 对象上调用 run 方法无法创建新的线程 Run 是线程的执行方法 必须创建 Thread 类的实例,以生成大量的新的线程 Q6 选择正确的输出结果 package 练习; import java.util.Scanner; public class x extends Thread{ public void run(){ try{ for(int i=1;i<5;i++){ System.out.println(i+" "); if(i>2){ interrupt(); sleep(1000); if(interrupted()) break; } } }catch(InterruptedException e){ System.out.println(" caugth"); } } public static void main(String[] args) { x x=new X(); x.start(); } Output 1 2 3 Caught 注释: interrupt()只是改变中断状态而已. interrupt()不会中断一个正在运行的线程。 这一方法 实际上完成的是,给受阻塞的线程抛出一个中断信号, 这样受阻线程就得以退出阻塞的状态。更确切 的说,如果线程被Object.wait, Thread.join 和Thread.sleep三种方法之一阻塞, 那么,它将接收到一个中断异常 (InterruptedException) ,从而提早地终结被阻塞状态。 如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到 InterruptedException异常(该线程必须事先预备好处理此状况) ,接着逃离阻塞状态。 线程A在执行sleep,wait,join时,线程B调用线程A的interrupt方法,的确这一个时候A会有 InterruptedException 异常抛出来. 但这其实是在sleep,wait,join这些方法内部会不断检查中断状态的值,而自己抛出的 InterruptedException。 如果线程A正在执行一些指定的操作时如赋值,for,while,if,调用方法等,都不会去检查中断 状态 ,所以线程A不会抛出 InterruptedException,而会一直执行着自己的操作. 当线程A终于执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException. 若没有调用sleep(),wait(),join()这些方法,即没有在线程里自己检查中断状态自己抛出 InterruptedException的 话, 那InterruptedException是不会被抛出来的. 具体使用见实例1,实例2。 注意1:当线程A执行到wait(),sleep(),join()时,抛出InterruptedException后,中断状态已经被 系统复位了, 线程A调用Thread.interrupted()返回的是false Q7 如何理解线程同步 package 练习; import java.util.Scanner; public class x { static Thread makeThread(final String id,boolean daemon){ Thread t=new Thread(id){ public void run(){ System.out.println(id); } }; t.setDaemon(daemon); t.start(); return t; } public static void main(String[] args) { Thread a=makeThread("A", false); Thread b=makeThread("b", true); System.out.println("END/"); } } Outout END/ A 线程共享了相同的资源 1 共享变量 要是多个线程在一个程序中有用, 必须有某种方法实现线程间互相通信或者共享结果, 最简单的 方法是使用共享变量。 使用同步来确保值从一个线程正确传播到另一个线程, 以及防止当一个线 程正在更新一些相关的数据项时,另一个线程看到不一致的中间结果。 2.存在某一个内容空间中的所有线程 线程与进程有许多共同点, 不同的是线程与同一进程中的其他线程共享相同的进程上下文, 包括 内存空间。只要访问工共享变量(静态变量或实例变量),线程就可以方便地互相交换数据,但 必须确保线程一受控的方式访问共享变量,以免它们互相干扰对方的更改 3 受控访问的同步 为了确保可以再线程之间手控方式共享数据,java 语言提供了两个关键字,synchronized 和 volatile、 Synchronized 有以下连个重要含义 一次只有一个线程可以执行代码的受保护部分 一个线程更改的数据对于其他线程是可见的。 若果没有同步,数据很容易就处于不一致状态。 4.确保共享数据更改的可见性 同步可以让用户确保县城看到一致的内存视图 处理器可以使用高速缓存加速对内存的访问 (或者编译器可以将值存储到寄存器中, 以便进行更 快的访问)。这表示在这样的系统上,对于同一变量,在两个不同处理器上执行的连个线程可能 会看到连个不同的值。 Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)单个实例的访问。当 一个变量被声明为 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主存,而任 何对该变量的读取也都绕过高速缓存,直接取自动主存。这表示是所有县城在任何时候看到的 volatile 变量值都相同。 5 用锁保护的原子代码块 Volatile 对于确保每个线程看到最新的变量值非常有用,但实际上经常需要保护代码片段,同 步使用监控器或锁的概念,已协调对特定代码块的访问。 每个 java 都有一个相关的锁,同一时间只能有一个线程持有 java 锁,当线程进入 synchronized,线程会阻塞并等待直到锁可用。当线程处于就绪状态时,并且获得锁后,将执 行代码块,当控制退出受保护的代码块,即达到了代码块末尾或者抛出没有在 synchronized 块中捕获的异常时,他就会释放锁 这样,每次只有一个线程可以执行受给定监控器保护的代码块。从其他线程的角度看,该代 码块可以看做是原子的,他要么全部执行,要么更本不执行 6 简单的同步示例 package 练习; import java.util.Scanner; public class x { private static Object lockObject=new Object(); private static class Thread1 extends Thread{ int x,y; public void run(){ synchronized(lockObject){ x=y=0; System.out.println(x); } } } private static class Thread2 extends Thread{ int x,y; public void run(){ synchronized(lockObject){ x=y=1; System.out.println(x); } } } public static void main(String[] args) { new Thread1().run(); new Thread2().run(); } } 将会打印 01 或者 10 如果没哟同步,他还会打印 11 或 00 7java 锁定 Java 锁定可以保护许多代码块或方法,每次只有一个线程就可以持有锁,如果两个线程正在等 待相同的锁,则他们不会同时执行该代码 在以下示例中, 连个线程可以同时不受限制地执行 setLastAccess () 方法中的 synchronized 块,因为每个线程有一个不同的 thengie 值。因此 synchronized 代码块受到两个正在执行的 线程不同锁的保护。 package 练习; import java.util.Date; import java.util.Scanner; public class x { public static class Thingie{ private Date lastAccess; public synchronized void setLastAccess(Date date){ this.lastAccess=date; } } public static class MyThread extends Thread{ private Thingie thingie; public MyThread(Thingie thingie){ this.thingie=thingie; } public void run(){ thingie.setLastAccess(new Date()); } } public static void main(String[] args) { Thingie thingie=new Thingie(); Thingie thingie2=new Thingie(); new MyThread(thingie).start(); new MyThread(thingie2).start(); } } 8 同步方法 创建 synchronized 块的最简单方法是将方法声明成 synchronized。这表示在进入方法主体 前,调用者必须获得锁。 9 同步的块 Synchronized 块的语法比 synchronized 方法稍微复杂一些, 因为还需要显示地指定锁要保护 哪个块。 Public class point{ Public void setXY(int x,int y){ Synchronized(this){ This.x=x; This .y=y; } } 使用 this 引用作为锁表示代码块将于这个类中的 synchronized 方法使用同一个锁 10 大多说类并没有同步 因为同不会带来小小的性能损失 Q 线程同步选择题 运行下面例子会输出什么 package 练习; import java.util.Date; import java.util.Scanner; public class X extends Thread { static Object lock1=new Object(); static Object lock2=new Object(); static volatile int i1,i2,j1,j2,k1,k2; public void run(){ while(true){ doit(); check(); } } void doit(){ synchronized (lock1) { i1++; } j1++; synchronized (lock2) { k1++; k2++; } j2++; synchronized (lock1) { i2++; } } void check(){ if(i1!=i2){ System.out.println("i"); } if(j1!=j2){ System.out.println("j"); } if(k1!=k2){ System.out.println("k"); } } public static void main(String[] args) { new X().start(); new X().start(); } } Outout output 在执行过程中不能确定打印字母 i,j,k 的哪一个。 Q9 下面哪些事件会导致线程死亡 A sleep() B wait() C Start() D run E 线程构造执行结束 在 java 线程中,正在运行的线程可以转化为不可运行状态,八十线程不能够从不可运行状态直 接转化为运行状态,而是首先转化为准备运行状态。 Interrupt():中断调度此方法线程。在等待通知、休眠或者阻塞以等待挂起完成的状态,线 程会抛出 InterruptedException 异常 Yield() :是线程暂时停止运行 1 线程调度的优先级 Java 将线程的优先级分为 1 个等级,分别是 1-10 之间的数字表示。数字越大表示等级越高。 当一个线程对象被创建时,默认的线程优先级是 5 为了控制现成的运行策略, java 定义了线程调度器来监控系统中处于就绪状态的所有线程。 线程调度器采用“抢占优先级”策略来调度线程执行。 具有相同优先级的所有线程采用轮状的 方式同分配 cpu 时间片。 使用 setPriority()方法改变线程的优先级。 在 Java 中比较特殊的线程被称为守护线程 deaemon 线程的低级线程。这个线程具有最低的 优先级。用于系统中的其他对象和线程提供服务。设置为一个守护线程方式是,在线程对象 创建之前调用 setdaemon 方法。Jvm 中的系统资源自动回收线程,他始终是在低级别的状态 中运行。用于实施监控和管理系统中的可回收资源。 2.运行和挂起 线程通过 start 方法调用后,线程就进入了准备运行状态。进入准备运行阶段后,线程才能 获得· 运行的资格,即获得 cpu 的时间 系统进程调度器来决定哪个线程可以运行,并确定其运行时间,也就是说,线程具有不可预 测性。 调用 yield 方法将会导致当前线程从运行状态转化为准备运行状态。 使用 yield 方法的好处就 是可以让出 cpu 的时间,供其他线程使用。 package 练习; import java.util.Date; import java.util.Scanner; public class X extends Thread { public static void main(String []args){ Mythread3 mythread32=new Mythread3("t2"); Mythread3 mythread3=new Mythread3("t1"); mythread3.start(); mythread32.start(); } } class Mythread3 extends Thread{ public Mythread3(String s) { // TODO Auto-generated constructor stub super(s); } public void run(){ for(int i=0;i<100;i++){ System.out.println(getName()+": "+i); if(i%10==0){yield(); }}} } t1: 0 t1: 1 t1: 2 t1: 3 t1: 4 t1: 5 t1: 6 t1: 7 t1: 8 t1: 9 t1: 10 t1: 11 t1: 12 t1: 13 t1: 14 t1: 15 t1: 16 t1: 17 t1: 18 t2: 0 t2: 1 t2: 2 t2: 3 sleep()