java解惑——易错知识点归纳总结
2012-09-25 15:27
267 查看
纯粹个人观点,如果有误,欢迎指正!
2.Default 可放在switch中的任何一个地方,但只有给定的条件匹配不到时,才会执行
3.Case,default语句如果执行完要跳出,必须用break, 没的话会向下继续执行(如果碰到case语句则直接进入执行)
实例1:
A.0
B.1
C.2
D.4
E.6
What is the acceptable type for the variable i?
A.byte
B.long
C.float
D.double
E.object
F.A and B
G.C and D
String s = “ABC”;
S = s.subString(2); //会重新生成一个字符串对象
以上两句执行后在内存中会产生“两”个字符串对象 一个”ABC”,另一个是s指向的”AB”(注意s已不再指向”ABC”)
StringBuffer 定义的是字符串变量,其値可以改变,如下:
StringBuffer s1 = new StringBuffer(“ABC”);
S1 = s1.subString(2);
以上两句执行后在内存中只产生“一个”字符串对象: s指向的”AB”;
一个是通过 ” ” 方式在 编译期 产生,存放在常量池中
一个是通过new方式在 运行期 产生,存放在堆内存中
但在运行时只会通过new方式产生一个对象
如是引用类型,则传递的是引用本身的副本
1.在同一个类中
2.有多个同名的方法,
3.方法参数不同(参数的个数不同 或则 参数的类型不同)
实例:
A.private void setVar (int a, float c, int b) { }
B.protected void setVar (int a, int b, float c) { }
C.public int setVar (int a, float c, int b) (return a;)
D.public int setVar (int a, int b, float c) (return a;)
E.protected float setVar (int a, int b, float c) (return c;)
符合覆盖的条件:
1.在继承中
2.子类中的方法名和父类相同
3.子类中的方法参数和父类相同
4.子类中的方法返回类型和父类一样
5.子类的方法不能比父类抛出更多的异常
6.子类的方法访问范围大于或等于父类
覆盖值得注意的是如果子类中有一个方法名称和父类一样,但参数不同,那不叫覆盖,所以也就不受覆盖的条件限制(注意该方法可以存在)
1. 类加载时,初始化静态变量和静态区块,先父类后子类
2. 运行中当new出一个对象时,开始为对象分配空间并初始化实例变量,先父类后子类
3. 调用构造函数时,先执行父类的构造函数,再执行子类的构造函数,具体过程是调用子类的构造函数时,在第一行处会调用父类的构造函数(显式或隐式)
6-2. 初始化时各类型的变量初始化的値:
引用类型: null
基本类型:
当我们产生某个存储对象的数组时,真正产生的其实是个存储references的数组。此数组建立之后,其中的每一个reference皆会被自动设为某个特殊值。该值以关键字null表示。当Java看到null值,便将这个reference视为“不指向任何对象”。使用任何reference之前,你必须先将某个对象指派给它。如果你使用某个reference而其值为null,便会在执行期发生错误
数组在分配空间时就开始了初始化,初始化规则,基本类型按照6-2的规则进行初始化,引用类型类型全部初始化为null
6-4. java中的所有的实例变量都有系统默认初始化,所有的方法变量由方法本身进行初始化,且方法中的变量一定要初始化后才能应用
2. 每一个类都至少有一个构造函数,自己不定义,编译器也会给分配一个默认的不带参数的构造函数
3. 子类的构造函数一定会调用父类的构造函数,通过super()调用,或显式或隐式,显式调用的父类构造函数必须存在; 如果没有显式调用则编译器会自动在子类的构造函数第一行处加上super()这个隐式调用,这时要求父类一定要有不带参数的构造函数存在(如果父类自己定义了构造函数,但带有参数,编译时会报错)
例子:
2. 异常机制中try{}后一定要跟catch吗?
* 不一定,,但必须跟finally.也就是catch和finally必须跟其中一个
* 异常机制中try{}后一定要跟catch吗?
* 不一定,,但必须跟finally.也就是catch和finally必须跟其中一个
* try {
* }finally {}
* 这样没问题,而且,可不是没有意义哦,因为这样可以保证即使发生了异常,finally里面的代码一定会被执行。
* 有时候,这个还是非常有用的。
* 比如可以用来释放一些自己占用的资源,然后让调用者处理异常。
3. 异常中的finally一定会执行,哪怕一个方法中有return语句,也是在异常处理后才返回
4. 异常的抛出可以先子类再父类,如果子类捕获了,则父类就不再捕获;
但是不能先父类再子类,那样会导致编译出错
5. 异常处理后,程序继续执行
实例:
逻辑运算如果操作符(&&, || )左边成立则就不在计算右边了
实例:
A.The program prints “0”
B.The program prints “4”
C.The program prints “8”
D.The program prints “12”
E.The code does not complete
= = 比较的是两个引用是否指向同一对象
String的存储特性会对以上的判定规则产生影响(实质上规则不变,表面上改变):
String 通过“”方式生成的对象会存储在常量池中,常量池有一个重要的特点就是共享,比如String s = “X”; 在把”X”放常量池之前jvm会检测常量池中是否存在和“X"相同的对象,如果已经存在则直接把引用指向已存在的对象,不再为”X”分配空间,好处是节约了空间
何时需要重写equals()
当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。
2.设计equals()
[1]使用instanceof操作符检查“实参是否为正确的类型”。
[2]对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。
[2.1]对于非float和double类型的原语类型域,使用==比较;
[2.2]对于对象引用域,递归调用equals方法;
[2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
[2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;
[2.5]对于数组域,调用Arrays.equals方法。
3.当改写equals()的时候,总是要改写hashCode()
根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
4.设计hashCode()
[1]把某个非零常数值,例如17,保存在int变量result中;
[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):
[2.1]boolean型,计算(f ? 0 : 1);
[2.2]byte,char,short型,计算(int);
[2.3]long型,计算(int) (f ^ (f>>>32));
[2.4]float型,计算Float.floatToIntBits(afloat);
[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
[2.6]对象引用,递归调用它的hashCode方法;
[2.7]数组域,对其中每个元素调用它的hashCode方法。
[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;
[4]返回result。
当我们给出一个整数,且该整数后不带l标示,则编译器自动把它视为int类型,如
Int i = 1 ; 是成立的
当我们给出一个小数,且该小数后不带f标示,则编译器自动把它视为double类型,如
Double d = 1.0; 是成立的
Double d = 1.0f; //正确, 小转大,自动
Float f = 1.0d(或1.0); //错误,大转小,需强制 float f = (float)1.0d;
(第一次被请求)
客户提出请求 -> web容器解析请求,找出请求的url,根据web.xml配置找到对应的servlet -> 加载servlet -> 实例化 -> 调用init初始化 -> 调用service方法 -> 由service方法自动匹配doXXX方法-> web容器关闭/servlet长时间没有被请求则调用其destroy方法销毁servlet实例
不确定的地方: servlet多长时间没有被调用才会销毁,可以设置吗? 不同的web服务器应该是不同的吧
Servlet是在java代码中嵌入HTML, 擅长逻辑控制
Jsp是在HTML中嵌入java代码, 擅长页面处理
Redirect是客户端的请求,客户端根据服务器传回的地址,重新向服务器发出请求,浏览器的地址栏显示的是新请求的url地址, 从http协议的角度,发生了两次请求响应的过程 )
-------------------
1. 重定向是HTTP协议定义的功能,要经过两次HTTP通信过程,第一次用于获取资源的实际地址,第二次用之前得到的地址发出请求, 这个过程对浏览器是可见的;
请求转发是servlet技术本身的特点,转发的过程是在服务器内部进行,对浏览器是不透明的,它认为它所发送的地址实际上得到的就是这些内容; 从HTTP角度看,只有一次通讯过程
2. 重定向只能转向新的资源,功能较单一; 请求转发不但可以转向新的资源,也可将其它资源和本身的生成的内容结合起来,功能很丰富
1.同步的概念: 当多个线程同时使用一个对象时,由于线程本身运行的不确定性,可能会造成操作的不完整性,故而引入同步
2.Java中同步的方式有两种, Synchronized 和 Lock
3.当一个线程进入一个对象的同步方法后,它会把该对象锁住,其它的线程不能再使用该对象(包括对象的任何方法,属性),直到该线程释放掉锁,其它线程才有机会使用该对象
4.一个线程释放同步锁的条件:
a. 正常运行完(退出synchronized块)
b. 使用wait()方法
5.同步中的方法: wait(), notify()/notifyAll(),用于同步中的线程通讯
Wait(): 释放持有的同步锁,本身进入锁等待状态,在线程中因为多个线程“同时“运作,可能导致运作的条件不满足,当条件不满足时,线程本身就需要进入等待状态(释放掉锁),等其它的线程改变了条件,它才能有机会继续执行
NotifyAll(): 唤醒锁等待的线程,当一个持有线程锁的对象调用该方法后,其它处于锁等待的线程虽然被唤醒,但其本身不会立刻释放掉锁,需要等运行结束后(退出synchronized块)才释放掉,其它线程才有机会执行
16-2.线程中的方法
6.sleep() ,当前线程休眠一个设定的时间,时间到后进入线程就绪队列,等待执行
7.join(),该方法使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。注意该方法也要捕获异常。
实例:
A.a.wait();
B.t.wait();
C.t.join();
D.t.yield();
E.t.notify();
F.a.notify();
G.t.interrupt();
8.yield(),与sleep()类似,只是不能由用户指定暂停多长时间,所以一个线程线程执行yield()方法后,也可能立刻执行(jvm还是分配给它执行),yield()方法只能让同优先级的线程有执行的机会。
举几个例子:
对例2的分析【网友】:
当一个线程执行下面方法:
这个时候他获得2个锁,一个是Stack对象的锁,还有list对象的锁,而notify,释放的是stack对象的锁,没有释放list对象的锁,所以只要当pop方法中检测到list的大小为0,则执行pop的线程会一直控制list的锁,使得push没法执行。 之所以大部分时间程序运行成功,是因为push总比pop快,list没有为0.
对例3的分析【网友】
如果没有出现死锁,那么输出的结果必然是:"ABABCD"; 如果没有输出此结果;那么死锁
原因:T1启动在前, T2启动在后;且T1的第一步操作为:synchronized(s1)
1. 由于T1执行过慢---> T2要执行第一步:synchronized(s2)--->寻找s1,被T1锁住等待--->T1寻找说说s2,被T2锁住等待 ; 出现死锁
2. T1执行过快-->s1,s2都被锁住--->T2执行,等待-->T1执行完:"AB" -->T2执行:"ABCD"
1. 它是遵从http协议的一个服务器端程序,按照http提供基本的请求解析、应答处理等
2. 它提供了供web程序运行的最直接的环境,比如tomcat就是一个servlet的容器
3. 它提供了对线程的管理,包括创建,调度,撤销等
4. 它提供请求地址与具体地址的对应处理
。。。
B/S是在C/S架构基础上发展起来的一种技术,相比C/S,B/S主要有以下几点的不同,这里不比较哪个优越,况且优越与否是和具体环境关联的,单独不能说哪个好,
1. B/S是遵从http协议的,即采用的是标准的协议,
2. B/S的客户端已开发好(就遵从http协议的浏览器),不需要程序员再开发
3. B/S的服务器端业界也提供了基础功能的实现(各种web容器)
一个C/S示例(多线程),希望在它的基础上能更好的理解web服务器
1. 客户端程序:MultiTalkClient.java
2. 服务器端程序: MultiTalkServer.java
3. 程序ServerThread.java
提高性能
2.资源池运作机制
由资源池管理器提供一定数目的目标资源,当有请求该资源时,资源池分配给一个,然后给该资源标识为忙,标示为忙的资源不能再被分配使用,当某一个资源使用完后,资源池把相关的资源的忙标示清除掉,以示该资源可以再被下一个请求使用
3.资源池常有的参数
1.初始资源的数目:资源池启动时,一次建立的资源数目,资源池最少要保证在这个数目上
2.最大资源的数目:当请求的资源超出这个数目,就等待
4.常见的资源池
1.数据库连接池
2.web容器中的request,response对象池
3.web容器中的线程池
一. Switch
1.其能接受的数据类型有四个,char , byte, short, int2.Default 可放在switch中的任何一个地方,但只有给定的条件匹配不到时,才会执行
3.Case,default语句如果执行完要跳出,必须用break, 没的话会向下继续执行(如果碰到case语句则直接进入执行)
实例1:
int i = 1, j = 0; switch (i) { case 2: j += 6; case 4: j += 1; default: j += 2; case 0: j += 4; }What is the value of j ?
A.0
B.1
C.2
D.4
E.6
What is the acceptable type for the variable i?
A.byte
B.long
C.float
D.double
E.object
F.A and B
G.C and D
二. String 和 StringBuffer
String 定义的是字符串常量,其値一旦定义就不再改变,如下:String s = “ABC”;
S = s.subString(2); //会重新生成一个字符串对象
以上两句执行后在内存中会产生“两”个字符串对象 一个”ABC”,另一个是s指向的”AB”(注意s已不再指向”ABC”)
StringBuffer 定义的是字符串变量,其値可以改变,如下:
StringBuffer s1 = new StringBuffer(“ABC”);
S1 = s1.subString(2);
以上两句执行后在内存中只产生“一个”字符串对象: s指向的”AB”;
三. String s = new String(“XYZ”) 产生了几个对象
该语句会产生2个字符串对象:一个是通过 ” ” 方式在 编译期 产生,存放在常量池中
一个是通过new方式在 运行期 产生,存放在堆内存中
但在运行时只会通过new方式产生一个对象
四. java中的参数只能“按値”传递,且传递的是値的 copy
如是基本类型,则传递的是基本类型的副本如是引用类型,则传递的是引用本身的副本
五. 方法重载和覆盖的条件
符合重载的条件:1.在同一个类中
2.有多个同名的方法,
3.方法参数不同(参数的个数不同 或则 参数的类型不同)
实例:
public class MethodOver { public void setVar (int a, int b, float c) { } }Which two overload the setVar method? (Choose Two)
A.private void setVar (int a, float c, int b) { }
B.protected void setVar (int a, int b, float c) { }
C.public int setVar (int a, float c, int b) (return a;)
D.public int setVar (int a, int b, float c) (return a;)
E.protected float setVar (int a, int b, float c) (return c;)
符合覆盖的条件:
1.在继承中
2.子类中的方法名和父类相同
3.子类中的方法参数和父类相同
4.子类中的方法返回类型和父类一样
5.子类的方法不能比父类抛出更多的异常
6.子类的方法访问范围大于或等于父类
覆盖值得注意的是如果子类中有一个方法名称和父类一样,但参数不同,那不叫覆盖,所以也就不受覆盖的条件限制(注意该方法可以存在)
六. java类中的变量初始化相关的知识
6-1.初始化顺序分三步:1. 类加载时,初始化静态变量和静态区块,先父类后子类
2. 运行中当new出一个对象时,开始为对象分配空间并初始化实例变量,先父类后子类
3. 调用构造函数时,先执行父类的构造函数,再执行子类的构造函数,具体过程是调用子类的构造函数时,在第一行处会调用父类的构造函数(显式或隐式)
6-2. 初始化时各类型的变量初始化的値:
引用类型: null
基本类型:
boolean : false char:\u0000 byte: 0 short: 0 int: 0 long: 0 float: 0.0 double: 0.06-3. 数组的初始化
当我们产生某个存储对象的数组时,真正产生的其实是个存储references的数组。此数组建立之后,其中的每一个reference皆会被自动设为某个特殊值。该值以关键字null表示。当Java看到null值,便将这个reference视为“不指向任何对象”。使用任何reference之前,你必须先将某个对象指派给它。如果你使用某个reference而其值为null,便会在执行期发生错误
数组在分配空间时就开始了初始化,初始化规则,基本类型按照6-2的规则进行初始化,引用类型类型全部初始化为null
6-4. java中的所有的实例变量都有系统默认初始化,所有的方法变量由方法本身进行初始化,且方法中的变量一定要初始化后才能应用
七. java中的构造函数
1. 构造函数不能被继承2. 每一个类都至少有一个构造函数,自己不定义,编译器也会给分配一个默认的不带参数的构造函数
3. 子类的构造函数一定会调用父类的构造函数,通过super()调用,或显式或隐式,显式调用的父类构造函数必须存在; 如果没有显式调用则编译器会自动在子类的构造函数第一行处加上super()这个隐式调用,这时要求父类一定要有不带参数的构造函数存在(如果父类自己定义了构造函数,但带有参数,编译时会报错)
例子:
class super1{ public int I = 0; public super1 (String text){ I = 1; } } public class sub1 extends super1{ public sub1(String text){ // super(text); I= 2; //隐式超级构造super1()是未定义的。必须明确援引另一个构造 } public static void main (String args[]){ sub1 sub2 = new sub1("Hello"); System.out.println(sub2.I); } }
八. java中的异常处理
1. java中的异常分运行时异常 和 非运行时异常, 运行时异常由运行时系统捕获并处理(编译正常),非运行时异常必须由处理(抛出或捕获)2. 异常机制中try{}后一定要跟catch吗?
* 不一定,,但必须跟finally.也就是catch和finally必须跟其中一个
* 异常机制中try{}后一定要跟catch吗?
* 不一定,,但必须跟finally.也就是catch和finally必须跟其中一个
* try {
* }finally {}
* 这样没问题,而且,可不是没有意义哦,因为这样可以保证即使发生了异常,finally里面的代码一定会被执行。
* 有时候,这个还是非常有用的。
* 比如可以用来释放一些自己占用的资源,然后让调用者处理异常。
3. 异常中的finally一定会执行,哪怕一个方法中有return语句,也是在异常处理后才返回
4. 异常的抛出可以先子类再父类,如果子类捕获了,则父类就不再捕获;
但是不能先父类再子类,那样会导致编译出错
5. 异常处理后,程序继续执行
实例:
/* * 非运行时异常一旦抛出,要么用catch块捕获处理,要么声明抛出 */ import java.io.IOException; public class ExceptionTest { // public static void methodA(){ public static void methodA() throws IOException { // throw new NullPointerException(); // try{ throw new IOException(); // System.out.println("method exit"); // }catch(IOException e){} // finally{} } public static void main(String[] args) { try { methodA(); // throw new IOException(); } catch (IOException e) { System.out.println("Caught1 IOException "); } catch (NullPointerException e) { System.out.println("Caught1 NullPointerException"); } catch (Exception e) { System.out.println("Caught Exception"); } System.out.println("main exit"); } }
九. 按位运算和逻辑运算
按位运算操作符(& ,| )两边的都要计算逻辑运算如果操作符(&&, || )左边成立则就不在计算右边了
实例:
public class test { private static int j = 0; private static boolean methodB(int k) { j += k; return true; } public static void methodA(int i) { boolean b; b = i < 10 | methodB(4); b = i < 10 || methodB(8); } public static void main(String args[]) { methodA(0); System.out.println(j); } }What is the result?
A.The program prints “0”
B.The program prints “4”
C.The program prints “8”
D.The program prints “12”
E.The code does not complete
十. for(;;)意义
相当于while(true), 不知道java为什么要搞出这个古怪让人费解的东西?十一. equals, = =
equals比较两个对象的内容是否相等= = 比较的是两个引用是否指向同一对象
String的存储特性会对以上的判定规则产生影响(实质上规则不变,表面上改变):
String 通过“”方式生成的对象会存储在常量池中,常量池有一个重要的特点就是共享,比如String s = “X”; 在把”X”放常量池之前jvm会检测常量池中是否存在和“X"相同的对象,如果已经存在则直接把引用指向已存在的对象,不再为”X”分配空间,好处是节约了空间
何时需要重写equals()
当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。
2.设计equals()
[1]使用instanceof操作符检查“实参是否为正确的类型”。
[2]对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。
[2.1]对于非float和double类型的原语类型域,使用==比较;
[2.2]对于对象引用域,递归调用equals方法;
[2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
[2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;
[2.5]对于数组域,调用Arrays.equals方法。
3.当改写equals()的时候,总是要改写hashCode()
根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
4.设计hashCode()
[1]把某个非零常数值,例如17,保存在int变量result中;
[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):
[2.1]boolean型,计算(f ? 0 : 1);
[2.2]byte,char,short型,计算(int);
[2.3]long型,计算(int) (f ^ (f>>>32));
[2.4]float型,计算Float.floatToIntBits(afloat);
[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
[2.6]对象引用,递归调用它的hashCode方法;
[2.7]数组域,对其中每个元素调用它的hashCode方法。
[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;
[4]返回result。
十二. 基本类型的变量赋初始値
Byte的范围为-128~127当我们给出一个整数,且该整数后不带l标示,则编译器自动把它视为int类型,如
Int i = 1 ; 是成立的
当我们给出一个小数,且该小数后不带f标示,则编译器自动把它视为double类型,如
Double d = 1.0; 是成立的
十三. 基本类型的转化
规则: 小的可以自动转化为大的, 大的要强制性才能转为小的,比如以下Double d = 1.0f; //正确, 小转大,自动
Float f = 1.0d(或1.0); //错误,大转小,需强制 float f = (float)1.0d;
十四. servlet运行机理
Servlet是java引入的在B/S架构中用来处理动态网页的一种技术,其实质是一个继承了HttpServlet的java类,由web容器负责解释运行,其机理如下:(第一次被请求)
客户提出请求 -> web容器解析请求,找出请求的url,根据web.xml配置找到对应的servlet -> 加载servlet -> 实例化 -> 调用init初始化 -> 调用service方法 -> 由service方法自动匹配doXXX方法-> web容器关闭/servlet长时间没有被请求则调用其destroy方法销毁servlet实例
不确定的地方: servlet多长时间没有被调用才会销毁,可以设置吗? 不同的web服务器应该是不同的吧
十五. servlet 和 jsp 的区别
都是用来处理动态网页的技术,jsp被编译后转化为servlet, 一个jsp页面本质上也是一个servlet;jsp在第一次被请求后,先转化为servlet,再编译,所以第一次要比servlet慢Servlet是在java代码中嵌入HTML, 擅长逻辑控制
Jsp是在HTML中嵌入java代码, 擅长页面处理
十六. forward(请求转发)与redirect(重定向)的区别
( Forward是服务器端请求,是servlet提供的一种技术,服务器根据请求的url找到请求的页面,对浏览器而言,这一过程是不透明的,好像什么也没发生一样,浏览器的地址栏不会显示被请求的url页面地址(显示的仍是上次请求的服务器端的url地址),从HTTP协议的角度,只发生一次的请求响应过程Redirect是客户端的请求,客户端根据服务器传回的地址,重新向服务器发出请求,浏览器的地址栏显示的是新请求的url地址, 从http协议的角度,发生了两次请求响应的过程 )
-------------------
1. 重定向是HTTP协议定义的功能,要经过两次HTTP通信过程,第一次用于获取资源的实际地址,第二次用之前得到的地址发出请求, 这个过程对浏览器是可见的;
请求转发是servlet技术本身的特点,转发的过程是在服务器内部进行,对浏览器是不透明的,它认为它所发送的地址实际上得到的就是这些内容; 从HTTP角度看,只有一次通讯过程
2. 重定向只能转向新的资源,功能较单一; 请求转发不但可以转向新的资源,也可将其它资源和本身的生成的内容结合起来,功能很丰富
十七. 线程
16-1.线程的同步1.同步的概念: 当多个线程同时使用一个对象时,由于线程本身运行的不确定性,可能会造成操作的不完整性,故而引入同步
2.Java中同步的方式有两种, Synchronized 和 Lock
3.当一个线程进入一个对象的同步方法后,它会把该对象锁住,其它的线程不能再使用该对象(包括对象的任何方法,属性),直到该线程释放掉锁,其它线程才有机会使用该对象
4.一个线程释放同步锁的条件:
a. 正常运行完(退出synchronized块)
b. 使用wait()方法
5.同步中的方法: wait(), notify()/notifyAll(),用于同步中的线程通讯
Wait(): 释放持有的同步锁,本身进入锁等待状态,在线程中因为多个线程“同时“运作,可能导致运作的条件不满足,当条件不满足时,线程本身就需要进入等待状态(释放掉锁),等其它的线程改变了条件,它才能有机会继续执行
NotifyAll(): 唤醒锁等待的线程,当一个持有线程锁的对象调用该方法后,其它处于锁等待的线程虽然被唤醒,但其本身不会立刻释放掉锁,需要等运行结束后(退出synchronized块)才释放掉,其它线程才有机会执行
16-2.线程中的方法
6.sleep() ,当前线程休眠一个设定的时间,时间到后进入线程就绪队列,等待执行
7.join(),该方法使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。注意该方法也要捕获异常。
实例:
class A implements runable { int i; public void run() { try { thread.sleep(5000); i = 10; } catch (InterruptedException e) { } } } public class Test { public static void main(string args[]) { try { A a = new A(); Thread t = new Thread(a); t.start(); int j = a.i; } catch (Exception e) { } } }Which statement al line 17 will ensure that j=10 at line 19?
A.a.wait();
B.t.wait();
C.t.join();
D.t.yield();
E.t.notify();
F.a.notify();
G.t.interrupt();
8.yield(),与sleep()类似,只是不能由用户指定暂停多长时间,所以一个线程线程执行yield()方法后,也可能立刻执行(jvm还是分配给它执行),yield()方法只能让同优先级的线程有执行的机会。
举几个例子:
//例1 线程同步 class SyncStack{ //同步堆栈类 private int index = 0; //堆栈指针初始值为0 private char []buffer = new char[6]; //堆栈有6个字符的空间 public synchronized void push(char c){ //加上互斥锁 while(index = = buffer.length){ //堆栈已满,不能压栈 try{ this.wait(); //等待,直到有数据出栈 }catch(InterruptedException e){} } this.notify(); //通知其它线程把数据出栈 buffer[index] = c; //数据入栈 index++; //指针向上移动 } public synchronized char pop(){ //加上互斥锁 while(index ==0){ //堆栈无数据,不能出栈 try{ this.wait(); //等待其它线程把数据入栈 }catch(InterruptedException e){} } this.notify(); //通知其它线程入栈 index- -; //指针向下移动 return buffer[index]; //数据出栈 } } class Producer implements Runnable{ //生产者类 SyncStack theStack; //生产者类生成的字母都保存到同步堆栈中 public Producer(SyncStack s){ theStack = s; } public void run(){ char c; for(int i=0; i<20; i++){ c =(char)(Math.random()*26+'A'); //随机产生20个字符 theStack.push(c); //把字符入栈 System.out.println("Produced: "+c); //打印字符 try{ Thread.sleep((int)(Math.random()*1000)); /*每产生一个字符线程就睡眠*/ }catch(InterruptedException e){} } } } class Consumer implements Runnable{ //消费者类 SyncStack theStack; //消费者类获得的字符都来自同步堆栈 public Consumer(SyncStack s){ theStack = s; } public void run(){ char c; for(int i=0;i<20;i++){ c = theStack.pop(); //从堆栈中读取字符 System.out.println("Consumed: "+c); //打印字符 try{ Thread.sleep((int)(Math.random()*1000)); }catch(InterruptedException e){} } } } public class SyncTest{ public static void main(String args[]){ SyncStack stack = new SyncStack(); //下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象 Runnable source=new Producer(stack); Runnable sink = new Consumer(stack); Thread t1 = new Thread(source); //线程实例化 Thread t2 = new Thread(sink); //线程实例化 t1.start(); //线程启动 t2.start(); //线程启动 } } //例2.下面的代码在绝大部分时间内都运行得很正常,请问在什么情况下会出现问题?问题的根源在哪里? import java.util.LinkedList; public class Stack { LinkedList list = new LinkedList(); public synchronized void push(Object x) { synchronized (list) { list.addLast(x); notify(); } } public synchronized Object pop() throws Exception { synchronized (list) { if (list.size() <= 0) { wait(); } return list.removeLast(); } } }
对例2的分析【网友】:
当一个线程执行下面方法:
public synchronized void push(Object x) { synchronized(list) { list.addLast( x ); notify(); } }
这个时候他获得2个锁,一个是Stack对象的锁,还有list对象的锁,而notify,释放的是stack对象的锁,没有释放list对象的锁,所以只要当pop方法中检测到list的大小为0,则执行pop的线程会一直控制list的锁,使得push没法执行。 之所以大部分时间程序运行成功,是因为push总比pop快,list没有为0.
//例3.可能死锁的线程 public class SyncTest { public static void main(String[] args) { final StringBuffer s1 = new StringBuffer(); final StringBuffer s2 = new StringBuffer(); new Thread() { public void run() { synchronized (s1) { s2.append("A"); synchronized (s2) { s2.append("B"); System.out.print(s1); System.out.print(s2); } } } }.start(); new Thread() { public void run() { synchronized (s2) { s2.append("C"); synchronized (s1) { s1.append("D"); System.out.print(s2); System.out.print(s1); } } } }.start(); } }
对例3的分析【网友】
如果没有出现死锁,那么输出的结果必然是:"ABABCD"; 如果没有输出此结果;那么死锁
原因:T1启动在前, T2启动在后;且T1的第一步操作为:synchronized(s1)
1. 由于T1执行过慢---> T2要执行第一步:synchronized(s2)--->寻找s1,被T1锁住等待--->T1寻找说说s2,被T2锁住等待 ; 出现死锁
2. T1执行过快-->s1,s2都被锁住--->T2执行,等待-->T1执行完:"AB" -->T2执行:"ABCD"
十八.web服务器
Web服务器说白了就是提供web应用的基础功能:1. 它是遵从http协议的一个服务器端程序,按照http提供基本的请求解析、应答处理等
2. 它提供了供web程序运行的最直接的环境,比如tomcat就是一个servlet的容器
3. 它提供了对线程的管理,包括创建,调度,撤销等
4. 它提供请求地址与具体地址的对应处理
。。。
B/S是在C/S架构基础上发展起来的一种技术,相比C/S,B/S主要有以下几点的不同,这里不比较哪个优越,况且优越与否是和具体环境关联的,单独不能说哪个好,
1. B/S是遵从http协议的,即采用的是标准的协议,
2. B/S的客户端已开发好(就遵从http协议的浏览器),不需要程序员再开发
3. B/S的服务器端业界也提供了基础功能的实现(各种web容器)
一个C/S示例(多线程),希望在它的基础上能更好的理解web服务器
1. 客户端程序:MultiTalkClient.java
import java.io.*; import java.net.*; public class MultiTalkClient { public static void main(String args[]) { try{ Socket socket=new Socket("127.0.0.1",4700); //向本机的4700端口发出客户请求 BufferedReader sin=new BufferedReader(new InputStreamReader(System.in)); //由系统标准输入设备构造BufferedReader对象 PrintWriter os=new PrintWriter(socket.getOutputStream()); //由Socket对象得到输出流,并构造PrintWriter对象 BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream())); //由Socket对象得到输入流,并构造相应的BufferedReader对象 String readline; readline=sin.readLine(); //从系统标准输入读入一字符串 while(!readline.equals("bye")){ //若从标准输入读入的字符串为 "bye"则停止循环 os.println(readline); //将从系统标准输入读入的字符串输出到Server os.flush(); //刷新输出流,使Server马上收到该字符串 System.out.println("Client:"+readline); //在系统标准输出上打印读入的字符串 System.out.println("Server:"+is.readLine()); //从Server读入一字符串,并打印到标准输出上 readline=sin.readLine(); //从系统标准输入读入一字符串 } //继续循环 os.close(); //关闭Socket输出流 is.close(); //关闭Socket输入流 socket.close(); //关闭Socket }catch(Exception e) { System.out.println("Error"+e); //出错,则打印出错信息 } } }
2. 服务器端程序: MultiTalkServer.java
import java.io.*; import java.net.*; import ServerThread; public class MultiTalkServer{ static int clientnum=0; //静态成员变量,记录当前客户的个数 public static void main(String args[]) throws IOException { ServerSocket serverSocket=null; boolean listening=true; try{ serverSocket=new ServerSocket(4700); //创建一个ServerSocket在端口4700监听客户请求 }catch(IOException e) { System.out.println("Could not listen on port:4700."); //出错,打印出错信息 System.exit(-1); //退出 } while(listening){ //永远循环监听 new ServerThread(serverSocket.accept(),clientnum).start(); //监听到客户请求,根据得到的Socket对象和 客户计数创建服务线程,并启动之 clientnum++; //增加客户计数 } serverSocket.close(); //关闭ServerSocket } }
3. 程序ServerThread.java
import java.io.*; import java.net.*; public class ServerThread extends Thread{ Socket socket=null; //保存与本线程相关的Socket对象 int clientnum; //保存本进程的客户计数 public ServerThread(Socket socket,int num) { //构造函数 this.socket=socket; //初始化socket变量 clientnum=num+1; //初始化clientnum变量 } public void run() { //线程主体 try{ String line; BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream())); //由Socket对象得到输入流,并构造相应的BufferedReader对象 PrintWriter os=newPrintWriter(socket.getOutputStream()); //由Socket对象得到输出流,并构造PrintWriter对象 BufferedReader sin=new BufferedReader(new InputStreamReader(System.in)); //由系统标准输入设备构造BufferedReader对象 System.out.println("Client:"+ clientnum +is.readLine()); //在标准输出上打印从客户端读入的字符串 line=sin.readLine(); //从标准输入读入一字符串 while(!line.equals("bye")){ //如果该字符串为 "bye",则停止循环 os.println(line); //向客户端输出该字符串 os.flush(); //刷新输出流,使Client马上收到该字符串 System.out.println("Server:"+line); //在系统标准输出上打印该字符串 System.out.println("Client:"+ clientnum +is.readLine()); //从Client读入一字符串,并打印到标准输出上 line=sin.readLine(); //从系统标准输入读入一字符串 } //继续循环 os.close(); //关闭Socket输出流 is.close(); //关闭Socket输入流 socket.close(); //关闭Socket server.close(); //关闭ServerSocket }catch(Exception e){ System.out.println("Error:"+e); //出错,打印出错信息 } } }
十九.资源池的理解
1.资源池引入的目的(好处)提高性能
2.资源池运作机制
由资源池管理器提供一定数目的目标资源,当有请求该资源时,资源池分配给一个,然后给该资源标识为忙,标示为忙的资源不能再被分配使用,当某一个资源使用完后,资源池把相关的资源的忙标示清除掉,以示该资源可以再被下一个请求使用
3.资源池常有的参数
1.初始资源的数目:资源池启动时,一次建立的资源数目,资源池最少要保证在这个数目上
2.最大资源的数目:当请求的资源超出这个数目,就等待
4.常见的资源池
1.数据库连接池
2.web容器中的request,response对象池
3.web容器中的线程池
相关文章推荐
- java解惑——易错知识点归纳总结
- java解惑——易错知识点归纳总结
- 数据库知识点总结归纳
- 各类算法技巧核心代码,知识点归纳总结之最长公共子序列
- iOS中 项目开发易错知识点总结 韩俊强的博客
- 易错java知识点总结(持续更新)
- Java知识点总结归纳
- jQuery学习和知识点总结归纳
- jQuery知识点总结和归纳
- iOS中 项目开发易错知识点总结 韩俊强的博客
- 关于学习过程中小知识点的总结与归纳
- 索引知识点归纳总结
- 前端考试知识点归纳总结
- jQuery学习和知识点总结归纳
- 14.知识点总结归纳—基于CT117E开发板的STM32库函数编程
- Java易错知识点总结
- C++易错知识点总结(一)...
- img.onload知识点归纳总结
- Java知识点归纳总结
- 对java中一些关键的,容易迷惑的知识点的归纳总结