JAVA-Java基础知识总结_第1页
JAVA-Java基础知识总结_第2页
JAVA-Java基础知识总结_第3页
JAVA-Java基础知识总结_第4页
JAVA-Java基础知识总结_第5页
已阅读5页,还剩169页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1.JAVA的StringBuffer类

StringBuffer类和String一样,也用来代表字符串,只是由于StringBuffer

的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生

成新的对象,在内存使用上要优于String类。所以在实际使用时,

如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用

StringBuffer要更加适合一些。

在StringBuffer类中存在很多和String类一样的方法,这些方法在功

能上和String类中的功能是完全一样的。

但是有一个最显著的区别在于,对于StringBuffer对象的每次修改都会改变对

象自身,这点是和String类最大的区别。

另外由于StringBuffer是线程安全的,关于线程的概念后续有专门的

章节进行介绍,所以在多线程程序中也可以很方便的进行使用,但是程序的

执行效率相对来说就要稍微慢一些。

1、StringBuffer对象的初始化

StringBuffer对象的初始化不像String类的初始化一样,Java提供的有特殊的

语法,而通常情况下一般使用构造方法进行初始化。

例如:

StringBuffers=newStringBuffer();

这样初始化出的StringBuffer对象是一个空的对象。

如果需要创建带有内容的StringBuffer对象,则可以使用:

StringBuffers=newStringBuffer("abc’');

这样初始化出的StringBuffer对象的内容就是字符串“abc”。

需要注意的是,StringBuffer和String属于不同的类型,也不能直接进行强制

类型转换,下面的代码都是错误的:

StringBuffers="abc”;〃赋值类型不匹配

StringBuffers=(StringBuffer)^^abc^^;〃不存在继承关系,无法进

行强转

StringBuffer对象和String对象之间的互转的代码如下:

Strings="abc”;

StringBuffersbl=newStringBuffer(“123”);

StringBuffersb2=newStringBuffer(s);//String转换为StringBuffer

Stringsi=sbl.toString();//StringBuffer转换为String

2、StringBuffer的常用方法

StringBuffer类中的方法主要偏重于对于字符串的变化,例如追加、插入和删

除等,这个也是StringBuffer和String类的主要区别。

a>append方法

publicStringBufferappend(boolear)b)

该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连

接。调用该方法以后,StringBuffer对象的内容也发生改变,例如:

StringBuffersb=newStringBuffer(''abc");

sb.append(true);

则对象sb的值将变成“abctrue”。

使用该方法进行字符串的连接,将比String更加节约内容,例如应用于数据

库SQL语句的连接,例如:

StringBuffersb=newStringBuffer();

Stringuser="test”;

Stringpwd="123”;

sb.append(44select*fromuserinfowhereusername=")

.append(user)

.append^andpwd=")

.append(pwd);

这样对象sb的值就是字符串“select*fromuserinfowhere

username=testandpwd=123n。

b>deleteCharAt方法

publicStringBufferdeleteCharAt(intindex)

该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。

例如:

StringBuffersb=newStringBufferC4Tesf,);

sb.deleteCharAt(l);

该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字

符,剩余的内容组成一个新的字符串。所以对象sb的值变为"Tst”。

还存在一个功能类似的delete方法:

publicStringBufferdelete(intstartjntend)

该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引

值的区间。例如:

StringBuffersb=newStringBuffer(64TestString,,J;

sb.delete(1,4);

该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余

的字符形成新的字符串。则对象sb的值是“TString)

c、insert方法

publicStringBufferinsert(intoffset,booleanb)

该方法的作用是在StringBuffer对象中插入内

容,然后形成新的字符串。例如:

StringBuffersb=new

StringBuffer("TestString");

sb.insert(4,false);

该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符

串,则执行以后对象sb的值是"TestfalseString”。

d、reverse方法

publicStringBufferreverse()

该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例

如:

StringBuffersb=newStringBuffer("abc");

sb.reverse();

经过反转以后,对象sb中的内容将变为"cba\

e^setCharAt方法

publicvoidsetCharAt(intindex,charch)

该方法的作用是修改对象中索引值为index位

置的字符为新的字符ch。例如:

StringBuffersb=new

StringBuffer(“abc”);

sb-setCharAtfl/D5);

则对象sb的值将变成“aDc”。

f、trimToSize方法

publicvoidtrimToSize()

该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的

长度,减少空间的浪费。

总之,在实际使用时,String和StringBuffer各有优势和不足,可以

根据具体的使用环境,选择对应的类型进行使用。

2.Java中replace。、replaceFirst()和replaceAII()区别

str.replace(str中被替换的,替换后的字符)

replace和replaceAII是JAVA中常用的替换字符的方法,它们的区别是:

1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换

(CharSequence即字符串序列的意思,说白了也就是字符串);

2)replaceAII的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAII("\\d",

“*")把•个字符串所有的数字字符都换成星号;

相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字

符串,如果只想替换第一次出现的,可以使用replaceHrst。,这个方法也是基于规则表达式的

替换,但与replaceAII。不同的是,只替换第一次出现的字符串;

另外,如果replaceAII。和replaceFirst。所用的参数据不是基于规则表达式的,则与

replace。替换字符串的效果是一样的,

即这两者也支健待串的操作;

还有一点注意:

执行了替换操作后,源字符串的内容是没有发生改变的(因为String类是final类型的不可

改写,但可以把处理得到的结果赋值).

举例如下:

Stringsrc=newString,'ab4332c43d");

System.out.println(src.replace(n3',,"f"));=>ab4f2c4fd.

System.out.println(src.replace(,3',,f'));=>ab4f2c4fd.

System.out.println(src.replaceAII("\\d",,,f,'));=>abffafcffd.

,,

System.out.println(src.replaceAII("a'J'f"));=>fb43fc23d.

System.out.println(src.replaceFirst(',\\d,"f',));=>abf32c43d

System.out.println(src.replaceFirst(',4",,'h"));=>abh32c43d.

如何将字符串中的“'“替换成“\\":

Stringmsgln;

StringmsgOut;

,,,w

msgOut=msgln.replaceAII('\\\\J"\\\\\\\\);

原因:

'在java中是一个转义字符,所以需要用两个代表一个。例如System.out.println("\\");

只打印出个

但是'''也是正则表达式中的转义字符(replaceAII的参数就是正则表达式),需要用两个代

表一个。所以:\\\\被java转换成\\八\又被正则表达式转换成\。

同样

CODE:\\\\\\\\

Java:\\\\

Regex:\\

将字符串中的'/‘替换成'\'的几种方式:

msgOut=msgln.replaceAII('7","\\\\");

msgOut=msgln.replace('7","\\");

3.window.location和window.open的区另

window.location=""跳转后有后退功能

window.location.replace("")跳转后没有后退功

window.open("")要新的窗口打开链接

4.Class.forName的作用以及为什么要用它【转】

Fbstedon2010-03-0310:24火之光阅读(674)评论(0)编辑收藏.

Qass.forName(xxx.xx.xx)返回的是-,个类

首先你要明白在java里面任何class都要装载在虚拟机上才能运行。这句话就是装载类用的(和new

不一样,要分清楚)。

至于什么时候用,你可以考虑一下这个问题,给你一个字符串变量,它代表一个类的包名和类名,你

怎么实例化它?只有你提到的这个方法了,不过要再加一点。

Aa=(A)Class.forName("pacage.A").newlnstance();

这和你

Aa=newA();

是一样的效果。

关于补充的问题

答案是肯定的,jvm会执行静态代码段,你要记住一个概念,静态代码是和class绑定的,class装载

成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。

Qass.forName(xxx.xx.xx)返回的是-,个类

Qass.forName(xxx.xx.xx);的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态

代码段

动态加载和创建Qass对象,比如想根据用户输入的字符串来创建对象

Stringstr=用户输入的字符串

Classt=Qass.forName(str);

t.newlnstance();

在初始化一个类,生成•个实例的时候,newlnstance。方法和new关键字除了一个是方法,一个是

关键字外,最主要有什么区别?它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后

者是创建一个新类。那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重

用等软件设计思想。

Java中工厂模式经常使用newlnstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到

具体答案。例如:

classc=Qass.forName("Example");

factory=(Examplelnterface)c.newlnstance();

其中Exampieinterface是Example的接口,可以写成如下形式:

StringclassName="Example";

classc=Qass.forName(className);

factory=(Examplelnterface)c.newlnstance();

进一步可以写成如下形式:

StringclassName=readfromXMlConfig;〃从xml配置文件中获得字符串

classc=aass.forName(className);

factory=(Examplelnterface)c.newlnstance();

上面代码已经不存在Example的类名称,它的优点是,无论Example类怎么变化,上述代码不变,

甚至可以更换Example的兄弟类Example2,Examples,Example4......,只要他们继承

Exampleinterface就可以。

从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用

newlnstance()方法的时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面

两个步骤的正是Qass的静态方法forName()所完成的,这个静态方法调用了启动类加教器,即加载

javaAPI的那个加载器。

现在可以看出,newlnstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加

载某个类,然后实例化。这样分步的好处是显而易见的。我们可以在调用class的静态加载方法

forName时获得更好的灵活性,提供给了一种降耦的手段。

最后用最简单的描述来区分new关键字和newlnstance。方法的区别:

newlnstance:弱类型。低效率。只能调用无参构造。

new:强类型。相对高效。能调用任何public构造。

5.Hibernate包作用详解【转】

Fastedon2009-12-2311:32火之光阅读(168)评论(0)编辑收藏.

Hibernate•共包括了23个jar包,令人眼花缭乱。本文将详细讲解Hibernate每个jar包的作用,便于

你在应用中根据自己的需要进行取舍。

下载Hibernate,例如2.0.3稳定版本,解压缩,可以看到一个hibernate2.jar和lib目录下有22个jar

包:

•hibernate2.jar:

Hibernate的库,没有什么可说的,必须使用的jar包

•cglib-asm.jar:

CGLIB库,Hibernate用它来实现P0字节码的动态生成,非常核心的库,必须使用的jar包

•dom4j.jar:

dom4j是一个Java的XMLAPI,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的JavaXML

API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在

SourceForge上找到它。在IBMdeveloperWorks上面可以找到一篇文章,对主流的JavaXMLAPI进行的

性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。我早在将近两年之前就开始使用

doin4j,直到现在。如今你可以看到越来越多的Java软件都在使用doiMj来读写XML,特别值得一提的是

连Sun的JAXM也在用dom4jo这是必须使用的jar包,Hibernate用它来读写配置文件。

•odmg.jar:

0DMG是•个ORM的规范,Hibernate实现了ODMG规范,这是•个核心的库,必须使用的jar包。

•commons-collections,jar:

ApacheCommons包中的一个,包含了一些Apache开发的集合类,功能比java,uti1.*强大。必须使用的

jar包。

•commons-beanutils.jar:

ApacheCommons包中的一个,包含了一些Bean_L具类类。必须使用的jar包。

•commons-lang,jar:

ApacheCommons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。

•commons-logging,jar:

ApacheCommons包中的一个,包含了日志功能,必须使用的jar包。这个包本身包含了一个SimpleLogger,

但是功能很弱。在运行的时候它会先在CLASSPATH找log4j,如果有,就使用log4j,如果没有,就找JDKL4

带的java.util,logging,如果也找不到就用SimpleLoggerocommons-logging,jar的出现是一个历史的

的遗留的遗憾,当初Apache极力游说Sun把log4j加入JDK1.4,然而JDKL4项目小组已经接近发布JDK1.4

产品的时间了,因此拒绝了Apache的要求,使用自己的java.util,logging,这个包的功能比log4j差的

很远,性能也一般。后来Apache就开发出来了commons-logging,jar用来兼容两个logger。因此用

commons-logging,jar写的log程序,底层的Logger是可以切换的,你可以选择log4j,java.util,logging

或者它自带的SimpleLogger0不过我仍然强烈建议使用log4j,因为log4j性能很高,log输出信息时间

几乎等于System,oul,而处理一条log平均只需要5us。你可以在Hibernate的src目录卜找到Hibernate

已经为你准备好了的log4j的配置文件,你只需要到Apache网站去下载log4j就可以了。

commons-logging.jar也是必须的jar包。

使用Hibernate必须的jar包就是以上的这儿个,剩下的都是可选的。

•ant.jar:

Ant编译工具的jar包,用来编译Hibernate源代码的。如果你不准备修改和编译Hibernate源代码,那

么就没有什么用,可选的jar包

•optional,jar:

Ant的一个辅助包。

•c3p0.jar:

C3Po是•个数据库连接池,Hibernate可以配置为使用C3Po连接池。如果你准备用这个连接池,就需要这

个jar包。

•proxool.jar:

也是一个连接池,同上。

•commons-pool,jar,commons-dbcp.jar:

DBCP数据库连接池,Apache的Jakarta组织开发的,Tomcal4的连接池也是DBCP。

实际上Hibernate自己也实现了一个非常非常简单的数据库连接池,加上上面3个,你实际上可以在

Hibernate上选择4种不同的数据库连接池,选择哪•个看个人的偏好,不过DBCP可能更通用•些。另外

强调一点,如果在EJB中使用Hibernate,一定要用AppServer的连接池,不要用以上4种连接池,否则

容器管理事务不起作用。

•connector,jar:

JCA规范,如果你在AppServer上把Hibernate配置为Connector的话,就需要这个jar。不过实际上一

般AppServer肯定会带上这个包,所以实际上是多余的包。

•jaas.jar:

JAAS是用来进行权限验证的,已经包含在JDK1.4里面了。所以实际上是多余的包。

•jcs.jar:

如果你准备在Hibernate中使用JCS的话,那么必须包括它,否则就不用。

•jdbc2_0-stdext.jar:

JDBC2.0的扩展包,一般来说数据库连接池会用上它。不过AppServer都会带匕所以也是多余的。

•jta.jar:

JTA规范,当Hibernate使用JTA的时候需要,不过AppServer都会带上,所以也是多余的。

•junit.jar:

Junit包,当你运行Hibernate自带的测试代码的时候需要,否则就不用。

•xalan.jar,xerces.jar,xml-apis.jar:

Xerces是XML解析器,Xalan是格式化器,xml-apis实际上是JAXP。一般AppServer都会带上,JDKL4

也包含了解析器,不过不是Xerces,是Crimson,效率比较差,不过Hibemate用XML只不过是读取配置

文件,性能没什么紧要的,所以也是多余的。

6.JAVA多线程的问题以及处理【转】

Restedon2009-12-0317:43火之光阅读(603)评论(1)编辑收藏.

124多线程问题及处理

多线程编程为程序开发带来了很多的方便,但是也带来了一些问题,

这些问题是在程序开发过程中必须进行处理的问题。

这些问题的核心是,如果多个线程同时访问一个资源,例如变量、

文件等,时如何保证访问安全的问题。在多线程编程中,这种会被多个线程

同时访问的资源叫做临界资源。

下面通过一个简单的示例,演示多个线程访问临界资源时产生的问

题。在该示例中,启动了两个线程类DataThread的对象,该线程每隔200毫

秒输出一次变量n的值,并将n的值减少1。变量n的值存储在模拟临界资源

的Data类中,该示例的核心是两个线程类都使用同一个Data类的对象,这

样Data类的这个对象就是一个临界资源了。示例代码如下:

packagesynl;

/**

*模拟临界资源的类

*/

publicclassData{

publicintn;

publicData(){

n=60;

)

}

packagesynl;

/**

*测试多线程访问时的问题

*/

publicclassTestMulThreadl{

publicstaticvoidmain(String[]args){

Datadata=newData();

DataThreaddl=newDataThread(dataJ线程:T);

DataThreadd2=newDataThread(dataJ线程2");

)

)

packagesynl;

/**

*访问数据的线程

*/

publicclassDataThreadextendsThread{

Datadata;

Stringname;

publicDataThread(Datadata,Stringname){

this.data=data;

=name;

start();

)

publicvoidrun(){

try(

for(inti=0;i<10;i++){

System.out.printin(name+

":"+data.n);

data.n-;

Thread.sleep(200);

)

}catch(Exceptione){}

)

)

在运行时,因为不同情况下该程序的运行结果会出现不同,该程序

的一种执行结果为:

线程1:60

线程2:60

线程2:58

线程1:58

线程2:56

线程1:56

线程2:54

线程1:54

线程2:52

线程1:52

线程2:50

线程1:50

线程2:48

线程1:48

线程2:47

线程1:46

线程2:44

线程1:44

线程2:42

线程1:42

从执行结果来看,第一次都输出60是可以理解的,因为线程在执行

时首先输出变量的值,这个时候变量n的值还是初始值60,而后续的输出就

比较麻烦了,在开始的时候两个变量保持一致的输出,而不是依次输出n的

每个值的内容,而到将要结束时,线程2输出47这个中间数值。

出现这种结果的原因很简单:线程1改变了变量n的值以后,还没有

来得及输出,这个变量n的值就被线程2给改变了,所以在输出时看的输出都

是跳跃的,偶尔出现了连续。

出现这个问题也比较容易接受,因为最基本的多线程程序,系统只

保证线程同时执行,至于哪个先执行,哪个后执行,或者执行中会出现一个

线程执行到一半,就把CPU的执行权交给了另外一个线程,这样线程的执行

顺序是随机的,不受控制的。所以会出现上面的结果。

这种结果在很多实际应用中是不能被接受的,例如银行的应用,两

个人同时取一个账户的存款,一个使用存折、一个使用卡,这样访问账户的

金额就会出现问题。或者是售票系统中,如果也这样就出现有人买到相同座

位的票,而有些座位的票却未售出。

在多线程编程中,这个是一个典型的临界资源问题,解决这个问题

最基本,最简单的思路就是使用同步关键字synchronized□

synchronized关键字是一个修饰符,可以修饰方法或代码块,其的

作用就是,对于同一个对象(不是一个类的不同对象),当多个线程都同时调

用该方法或代码块时,必须依次执行,也就是说,如果两个或两个以上的线

程同时执行该段代码时,如果一个线程已经开始执行该段代码,则另外一个

线程必须等待这个线程执行完这段代码才能开始执行。就和在银行的柜台办

理业务一样,营业员就是这个对象,每个顾客就好比线程,当一个顾客开始

办理时,其它顾客都必须等待,及口寸这个正在办理的顾客在办理过程中接了

一个电话(类比于这个线程释放了占用CPU的时间,而处于阻塞状态),其它

线程也只能等待。

使用synchronized关键字修改以后的上面的代码为:

packagesyn2;

/**

*模拟临界资源的类

*/

publicclassData2{

publicintn;

publicData2(){

n=60;

)

publicsynchronizedvoidaction(Stringname){

System.out.println(name++n);

n-;

)

)

packagesyn2;

/**

*测试多线程访问时的问题

*/

publicclassTestMulThread2{

publicstaticvoidmain(String[]args){

Data2data=newData2();

Data2Threaddl=newData2Thread(data,"线程

1");

Data2Threadd2=newData2Thread(data,"线程

2");

)

)

packagesyn2;

/**

*访问数据的线程

*/

publicclassData2ThreadextendsThread{

Data2data;

Stringname;

publicData2Thread(Data2data,Stringname){

this.data=data;

=name;

start();

)

publicvoidrun(){

try(

for(inti=O;i<10;i++){

data.action(name);

Thread.sleep(200);

)

}catch(Exceptione){}

)

}

该示例代码的执行结果会出现不同,一种执行结果为:

线程1:60

线程2:59

线程2:58

线程1:57

线程2:56

线程1:55

线程2:54

线程1:53

线程2:52

线程1:51

线程2:50

线程1:49

线程1:48

线程2:47

线程2:46

线程1:45

线程2:44

线程1:43

线程2:42

线程1:41

在该示例中,将打印变量n的代码和变量n变化的代码组成一个专

门的方法action,并且使用修饰符synchronized修改该方法,也就是说对于一

个Data2的对象,无论多少个线程同时调用action方法时,只有一个线程完全

执行完该方法以后,别的线程才能够执行该方法。这就相当于一个线程执行

到该对象的synchronized方法时,就为这个对象加上了一把锁,锁住了这个

对象,别的线程在调用该方法时,发现了这把锁以后就继续等待下去了。

如果这个例子还不能帮助你理解如何解决多线程的问题,那么下面再来看

一个更加实际的例子——卫生间问题。

例如火车上车厢的卫生间,为了简单,这里只模拟一个卫生间,这

个卫生间会被多个人同时使用,在实际使用时,当一个人进入卫生间时则会

把卫生间锁上,等出来时打开门,下一个人进去把门锁上,如果有一个人在

卫生间内部则别人的人发现门是锁的则只能在外面等待。从编程的角度来看,

这里的每个人都可以看作是一个线程对象,而这个卫生间对象由于被多个线

程访问,则就是临界资源,在一个线程实际使用时,使用synchronized关键

将临界资源锁定,当结束时,释放锁定。实现的代码如下:

packagesyn3;

/**

*测试类

7

publicclassTestHuman{

publicstaticvoidmain(String[]args){

Toilett=newToilet();〃卫生间对象

Humanhl=newHuman("l",t);

Humanh2=newHuman("2",t);

Humanh3=newHuman("3",t);

}

)

packagesyn3;

/**

*人线程类,演示互斥

*/

publicclassHumanextendsThread{

Toilett;

Stringname;

publicHuman(Stringnamejoilett){

=name;

this.t=t;

start。;〃启动线程

)

publicvoidrun(){

〃进入卫生间

t.enter(name);

)

}

packagesyn3;

/**

*卫生间,互斥的演示

*/

publicclassToilet{

publicsynchronizedvoidenter(Stringname){

System.out.println(name+"已进入!");

try(

Thread.sleep(2000);

}catch(Exceptione){}

System.out.println(name+"离开!");

)

)

该示例的执行结果为,不同次数下执行结果会有所不同:

1已进入!

1离开!

3已进入!

3离开!

2已进入!

2离开!

在该示例代码中,Toilet类表示卫生间类,Human类模拟人,是该示

例中的线程类,TestHuman类是测试类,用于启动线程。在TestHuman中,

首先创建一个Toilet类型的对象t,并将该对象传递到后续创建的线程对象中,

这样后续的线程对象就使用同一个Toilet对象,该对象就成为了临界资源。

下面创建了三个Human类型的线程对象,每个线程具有自己的名称name参

数,模拟3个线程,在每个线程对象中,只是调用对象t中的enter方法,模

拟进入卫生间的动作,在enter方法中,在进入时输出调用该方法的线程进入,

然后延迟2秒,输出该线程离开,然后后续的一个线程进入,直到三个线程都

完成enter方法则程序结束。

在该示例中,同一个Toilet类的对象t的enter方法由于具有

synchronized修饰符修饰,则在多个线程同时调用该方法时,如果一个线程进

入到enter方法内部,则为对象t上锁,直到enter方法结束以后释放对该对

象的锁定,通过这种方式实现无论多少个Human类型的线程,对于同一个对

象3任何时候只能有一个线程执行enter方法,这就是解决多线程问题的第

一种思路——互斥的解决原理。

12.4.2同步

使用互斥解决多线程问题是一种简单有效的解决办法,但是由于该

方法比较简单,所以只能解决一些基本的问题,对于复杂的问题就无法解决

To

解决多线程问题的另外一种思路是同步。同步是另外一种解决问题

的思路,结合前面卫生间的示例,互斥方式解决多线程的原理是,当一个人

进入到卫生间内部时,别的人只能在外部时刻等待,这样就相当于别的人虽

然没有事情做,但是还是要占用别的人的时间,浪费系统的执行资源。而同

步解决问题的原理是,如果一个人进入到卫生间内部时,则别的人可以去睡

觉,不占用系统资源,而当这个人从卫生间出来以后,把这个睡觉的人叫醒,

则它就可以使用临界资源了。所以使用同步的思路解决多线程问题更加有

效,更加节约系统的资源。

在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费

者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费

者发现无内容可消费时则睡觉。下面举一个比较实际的例子一一生活费问题。

生活费问题是这样的:学生每月都需要生活费,家长一次预存一段

时间的生活费,家长和学生使用统一的一个帐号,在学生每次取帐号中一部

分钱,直到帐号中没钱时通知家长存钱,而家长看到帐户还有钱则不存钱,

直到帐户没钱时才存钱。在这个例子中,这个帐号被学生和家长两个线程同

时访问,则帐号就是临界资源,两个线程是同时执行的,当每个线程发现不

符合要求时则等待,并释放分配给自己的CPU执行时间,也就是不占用系统

资源。实现该示例的代码为:

packagesyn4;

/**

*测试类

*/

publicclassTestAccount{

publicstaticvoidmain(String[]args){

Accouta=newAccout();

StudentThreads=newStudentThread(a);

GenearchThreadg=newGenearchThread(a);

)

)

packagesyn4;

*模拟学生线程

*/

publicclassStudentThreadextendsThread{

Accouta;

publicStudentThread(Accouta){

this.a=a;

start();

)

publicvoidrun(){

try(

while(true){

Thread.sleep(2000);

a.getMoney();〃取钱

)

}catch(Exceptione){}

)

)

packagesyn4;

*家长线程

*/

publicclassGenearchThreadextendsThread{

Accouta;

publicGenearchThread(Accouta){

this.a=a;

start();

)

publicvoidrun(){

try(

while(true){

Thread.sleep(12000);

a.saveMoney。;〃存钱

}

}catch(Exceptione){}

)

}

packagesyn4;

/**

*银行账户

*/

publicclassAccout{

intmoney=0;

*

*取钱

*如果账户没钱则等待,否则取出所有钱提醒存钱

*/

publicsynchronizedvoidgetMoney(){

System.out.println("准备取钱!");

try(

iffmoney==0){

wait。;〃等待

)

〃取所有钱

System.out.printin("乘lj余:"+money);

money-=50;

〃提醒存钱

notify();

}catch(Exceptione){}

}

/**

*存钱

*如果有钱则等待,否则存入200提醒取钱

*/

publicsynchronizedvoidsaveMoney(){

System.out.println("准备存钱!");

try(

if(money!=0){

wait();〃等待

)

〃取所有钱

money=200;

System.out.printin("存入:"+money);

〃提醒存钱

notify();

}catch(Exceptione){}

)

)

该程序的一部分执行结果为:

准备取钱!

准备存钱!

存入:200

剩余:200

准备取钱!

剩余:150

准备取钱!

剩余:100

准备取钱!

剩余:50

准备取钱!

准备存钱!

存入:200

剩余:200

准备取钱!

剩余:150

准备取钱!

剩余:100

准备取钱!

剩余:50

准备取钱!

在该示例代码中,TestAccount类是测试类,主要实现创建帐户

Account类的对象,以及启动学生线程StudentThread和启动家长线程

GenearchThreado在StudentThread线程中,执行的功能是每隔2秒中取一次

钱,每次取50元。在GenearchThread线程中,执行的功能是每隔12秒存一次

钱,每次存200。这样存款和取款之间不仅时间间隔存在差异,而且数量上也

会出现交叉。而该示例中,最核心的代码是Account类的实现。

在Account类中,实现了同步控制功能,在该类中包含一个关键的

属性money,该属性的作用是存储帐户金额。在介绍该类的实现前,首先介

绍一下两个同步方法——wait和notify方法的使用,这两个方法都是Object

类中的方法,也就是说每个类都包含这两个方法,换句话说,就是Java天生

就支持同步处理。这两个方法都只能在synchronized修饰的方法或语句块内

部采用被调用。其中wait方法的作用是使调用该方法的线程休眠,也就是使

该线程退出CPU的等待队列,处于冬眠状态,不执行动作,也不占用CPU排

队的时间,notify方法的作用是唤醒一个任意该对象的线程,该线程当前处于

休眠状态,至于唤醒的具体是那个则不保证。在Account类中,被StudentThread

调用的getMoney方法的功能是判断当前金额是否是0,如果是则使

StudentThread线程处于休眠状态,如果金额不是0,则取出50元,同时唤醒

使用该帐户对象的其它一个线程,而被GenearchThread线程调用的

saveMoney方法的功能是判断当前是否不为0,如果是则使GenearchThread

线程处于休眠状态,如果金额是0,则存入200元,同时唤醒使用该帐户对象

的其它一个线程。

如果还是不清楚,那就结合前面的程序执行结果来解释一下程序执

行的过程:在程序开始执行时,学生线程和家长线程都启动起来,所以输出

“准备取钱”和“准备存钱”,然后学生线程按照该线程run方法的逻辑执

行,先延迟2秒,然后调用帐户对象a中的getMoney方法,但是由于初始情

况下帐户对象a中的money数值为0,所以学生线程就休眠了。在学生线程

执行的同时,家长线程也按照该线程的run方法的逻辑执行,先延迟12秒,

然后调用帐户对象a中的saveMoney方法,由于帐户a对象中的money为零,

条件不成立,所以执行存入200元,同时唤醒线程,由于使用对象a的线程现

在只有学生线程,所以学生线程被唤醒,开始执行逻辑,取出50元,然后唤

醒线程,由于当前没有线程处于休眠状态,所以没有线程被唤醒。同时家长

线程继续执行,先延迟12秒,这个时候学生线程执行了4次,耗时4X2秒=8秒,

就取光了帐户中的钱,接着由于帐户为0则学生线程又休眠了,一直到家长线

程延迟12秒结束以后,判断帐户为0,又存入了200元,程序继续执行下去。

在解决多线程问题是,互斥和同步都是解决问题的思路,如果需要

形象的比较这两种方式的区别的话,就看一下下面的示例。一个比较忙的老

总,桌子上有2部电话,在一部处于通话状态时,另一部响了,老总拿其这部

电话说我在接电话,你等一下,而没有挂电话,这种处理的方式就是互斥。

而如果老总拿其另一部电话说,我在接电话,等会我打给你,然后挂了电话,

这种处理的方式就是同步。两者相比,互斥明显占用系统资源(浪费电话费,

浪费别人的时间),而同步则是一种更加好的解决问题的思路。

12.4.3死锁

多线程编程在实际的网络程序开发中,在客户端程序实现中使用的

比较简单,但是在服务器端程序实现中却不仅是大量使用,而且会出现比客

户端更多的问题。

另外一个容易在服务器端出现的多线程问题是——死锁。死锁指两

个或两个以上的线程为了使用某个临界资源而无限制的等待下去。还是以前

面卫生间的例子来说明死锁,例如两个人都同时到达卫生间,而且两个人都

比较礼貌,第一个人和第二个人说:你先吧,第二个人和第一个人说:你先

吧。这两个人就这样一直在互相礼让,谁也不进入,这种现象就是死锁。这

里的两个人就好比是线程,而卫生间在这里就是临界资源,而由于这两个线

程在一直谦让,谁也不使用临界资源。

死锁不仅使程序无法达到预期实现的功能,而且浪费系统的资源,

所以在服务器端程序中危害比较大,在实际的服务器端程序开发中,需要注

意避免死锁。

而死锁的检测比较麻烦,而且不一定每次都出现,这就需要在测试

服务器端程序时,有足够的耐心,仔细观察程序执行时的性能检测,如果发

现执行的性能显著降低,则很可能是发生了死锁,然后再具体的查找死锁出

现的原因,并解决死锁的问题。

死锁出现的最本质原因还是逻辑处理不够严谨,在考虑时不是很周

全,所以一般需要修改程序逻辑才能够很好的解决死锁。

12.4.4线程优先级

在日常生活中,例如火车售票窗口等经常可以看到“XXX优先”,

那么多线程编程中每个线程是否也可以设置优先级呢?

在多线程编程中,支持为每个线程设置优先级。优先级高的线程在

排队执行时会获得更多的CPU执行时间,得到更快的响应。在实际程序中,

可以根据逻辑的需要,将需要得到及时处理的线程设置成较高的优先级,而

把对时间要求不高的线程设置成比较低的优先级。

在Thread类中,总计规定了三个优先级,分别为:

•MAX_PRIORITY------最高优先级

•NORM_PRIORITY——普通优先级,也是默认优先级

•MINPRIORITY-・最低优先级

在前面创建的线程对象中,由于没有设置线程的优先级,则线程默认的优

先级是NORM_PRIORITY,在实际使用时,也可以根据需要使用Thread类中的

setPriority方法设置线程的优先级,该方法的声明为:

publicfinalvoidsetPriority(intnewPriority)

假设t是一个初始化过的线程对象,需要设置t的优先级为最高,则实现

的代码为:

t.setPriority(Thread.MAX_PRIORITY);

这样,在该线程执行时将获得更多的执行机会,也就是优先执行。如果由

于安全等原因,不允许设置线程的优先级,则会抛出SecurityException异常。

下面使用一个简单的输出数字的线程演示线程优先级的使用,实现的示例

代码如下:

packagepriority;

/**

*测试线程优先级

*/

publicclassTestPriority{

publicstaticvoidmain(String[]args){

PrintNumberThreadpl=new

PrintNumberThread("高优先级)

PrintNumberThreadp2=new

PrintNumberThread("普通优先级”);

PrintNumberThreadp3=new

PrintNumberThread("低优先级”);

pl.setPriority(Thread.MAX_PRIORITY);

p2.setPriority(Thread.NORM_PRIORITY);

p3.setPriority(Thread.MIN_PRIORITY);

pl.start();

p2.start();

p3.start();

)

}

packagepriority;

/**

*输出数字的线程

*/

publicclassPrintNumberThreadextendsThread{

Stringname;

publicPrintNumberThread(Stringname){

=name;

)

publicvoidrun(){

try(

for(inti=0;i<10;i++){

System.out.printin(name+

)

}catch(Exceptione){}

)

)

程序的一种执行结果为:

高优先级:o

高优先级:1

高优先级:2

普通优先级:0

高优先级:3

普通优先级:1

高优先级:4

普通优先级:2

高优先级:5

高优先级:6

高优先级:7

高优先级:8

高优先级:9

普通优先级:3

普通优先级:4

普通优先级:5

普通优先级:6

普通优先级:7

普通优先级:

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论