java1.5新技术

来源:互联网 发布:mysql 触发器 性能 编辑:程序博客网 时间:2024/06/08 04:20

英文缩写

IDE: Integrated Development Environment

Java EE

Jms

Jmx

Jndi

 

 

编译和运行:

eclipse中,Preferences—》java,下的Complier指定编译环境,Installed JREs指定编译好的程序的运行环境。

高版本的Java可以运行低版本的javac编译的程序。

但底版本的Java无法运行高版本的javac编译的程序。会出现:

java.lang.UnsupportedClassVersionError: Bad version number in .class file

 

 

快捷键:

编码时设置快捷键(如输入syso后按Alt+/ 会变成System.out.println):

Preferences—》General -> keys 中选Content Assist。有些预设的快捷键会与其他冲突,所以要Remove Binding然后绑定新的快捷键。

 

Viewperspective

OutlinePackage Explorer这样的一个个小窗口称为View,一套View的集合组成一个perspective,如JavaDebug

 

代码模板:

选中某语句,能通过右击鼠标->surrounded with ->try等,自动加上try catch语句。这些自动补充的代码就是套用了代码模板。

代码模板可在Preferences -> Java -> Editor -> Templates中设置。

 

静态导入: import static

以前的import语句可以导入一个类或某个包中的所有类。

import static可以导入一个类中的某个静态方法或所有静态方法。

import static java.lang.Math.*; 导入Math下的所有静态方法。

 

 

 

 

可变参数

l 问题:一个方法接受的参数个数不固定,例如:

Ø System.out.println(countScore(2,3,5));

Ø System.out.println(countScore(1,2,3,5));

l 可变参数的特点:

Ø 只能出现在参数列表的最后;这个要记住

Ø ...位于变量类型和变量名之间,前后有无空格都可以;

Ø 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。

 

 

 

增强for循环

l 语法:

Ø for ( type 变量名:集合变量名 )  { … } 

l 注意事项:

Ø 迭代变量必须在( )中定义!

Ø 集合变量可以是数组或实现了Iterable接口的集合类

l 举例: 

public static int add(int x,int ...args) {

int sum = x;

for(int arg:args) {

sum += arg;

}

return sum;

}

 

 

 

基本数据类型的自动装箱与拆箱

l 自动装箱:

Ø Integer num1 = 12;

l 自动拆箱:

Ø System.out.println(num1 + 12);

l 基本数据类型的对象缓存:

Integer num1 = 12;

Integer num2 = 12; 这块相等,<=127都是真的

System.out.println(num1 == num2);

Integer num3 = 129;                这块不相等,因为是对象

Integer num4 = 129;

System.out.println(num3 == num4);

Integer num5 = Integer.valueOf(12);

Integer num6 = Integer.valueOf(12)  ;   这块的道理同上,213不适用。

System.out.println(num5 == num6);

 

这里需要注意的是第一个输出true,第二个输出为false,第三个输出为true。

因为对于一个字节(-128~127)内的数字采用了flyweight pattern,享元模式/蝇量模式。把数字包装成Integer对象缓存在一个池子里,下次需要用到把数字包装成对象时会先查该池子,有就取出来,所以会取到相同的对象。

Flyweight模式就是当需要许多相似的小对象时,把相同的属性变成一个对象,把不同的属性提取为方法的参数,称之为外部状态,相同的属性为内部状态。如word26个字母

字母i是一个对象,i.display(int x,int y) 表示i显示的位置。char i是它的内部状态,若不使用该模式,需要三个参数i{char,x,y},并不断new对象。相同的例子还有Windows文件夹图标。图标相同,不同的是标题。

 

 

枚 举

l 为什么要有枚举

Ø 问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。

Ø 枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。

l 用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。 

Ø 私有的构造方法

Ø 每个元素分别用一个公有的静态成员变量表示

Ø 可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句转移成了一个个独立的类。

public abstract class WeekDay {

  private WeekDay(){}

  public static final WeekDay SUN = new WeekDay(){

@Override

public WeekDay nextDay() {

return MON;

}

  };

  public static final WeekDay MON = new WeekDay(){

@Override

public WeekDay nextDay() {

return SUN;

}

  };

  public abstract WeekDay nextDay();

  //该方法也可以改写为抽象方法

public String toString(){

return this==SUN ? "SUN" : "MON";

  }

}

 

l 枚举的基本应用

Ø 举例:定义一个Weekday的枚举。

Ø 扩展:枚举类的values,valueOf,name,toString,ordinal等方法

Ø 总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象,例如可以调用WeekDay.SUN.getClass().getName和WeekDay.class.getName()。

 

枚举相当于一个类 ,枚举中的元素,相当于枚举的实例对象。

记住枚举的valueof这个静态方法,可以把字符串转换为枚举。

枚举的构造方法必须是私有的。

 

 

l 枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。

l 枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。

l 带构造方法的枚举

Ø 构造方法必须定义成私有的

Ø 如果有多个构造方法,该如何选择哪个构造方法?

Ø 枚举元素MON和MON()的效果一样,都是调用默认的构造方法。

l 带方法的枚举

Ø 定义枚举TrafficLamp

Ø 实现普通的next方法

Ø 实现抽象的next方法:每个元素分别是由枚举类的子类来生成的实例对象,这些子类采用类似内部类的方式进行定义。

Ø 增加上表示时间的构造方法

l 枚举只有一个成员时,就可以作为一种单例的实现方式。

 

 

有抽象方法和参数构造方法的枚举:

public enum TrafficLamp {

GREEN(3){

@Override

public TrafficLamp nextLamp() {

return YELLOW;

}

}

,YELLOW(1){

@Override

public TrafficLamp nextLamp() {

return RED;

}

},RED(2){

@Override

public TrafficLamp nextLamp() {

return GREEN;

}

};

private int time//亮灯时间长度

private TrafficLamp(int time){

this.time = time;

}

public abstract TrafficLamp nextLamp();

}

 

代码解释:

RED,YELLOW,GREENTrafficLamp的实例对象,其后的{}中定义了一个TrafficLamp的匿名子类,分别赋给了这些对象。

RED(2){

public TrafficLamp nextLamp(){

return GREEN;

}

}

意思是调用了父类TrafficLamp的带参构造方法,生成TrafficLamp的子类,最后把生成的子类赋给RED

 

 

补充,实例化子类语句

New Date(){} 这个语句表示实例化Date的子类,子类的构造方法调用父类无参数的构造方法。也可以New Date(300){},这时就调用父类带参数的构造方法。

 

 

 

反 射

反射的基石àClass类

l 对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

Ø 人àPerson

Ø Java类àClass

l  Class类代表Java类,它的各个实例对象又分别对应什么呢?

Ø 对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。

Ø 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?

l 如何得到各个字节码对应的实例对象( Class类型)

Ø 类名.class,例如,System.class

Ø 对象.getClass(),例如,new Date().getClass()

Ø Class.forName("类名"),例如,Class.forName("java.util.Date");

l 九个预定义Class实例对象:

Ø 参看Class.isPrimitive方法的帮助

Ø Int.class == Integer.TYPE

l 数组类型的Class实例对象

Ø Class.isArray()

l 总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…

 

 

 

Class的定义:

Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (booleanbytecharshortintlongfloat, and double), and the keyword void are also represented as Class objects. 

Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader

String str1 = "abc";

Class cls1 = str1.getClass();

Class cls2 = String.class;

Class cls3 = Class.forName("java.lang.String");

System.out.println(cls1 == cls2); //true

System.out.println(cls1 == cls3);  //true

System.out.println(cls1.isPrimitive()); //false

System.out.println(int.class.isPrimitive()); //true

System.out.println(int.class == Integer.class); //false

System.out.println(int.class == Integer.TYPE); //true

System.out.println(int[].class.isPrimitive()); //false

System.out.println(int[].class.isArray()); //true

 

 

反射

l 反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。 

l 一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

 

 

Constructor

l Constructor类代表某个类中的一个构造方法

l 得到某个类所有的构造方法:

Ø 例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();

l 得到某一个构造方法:

Ø 例子:      Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

//获得方法时要用到类型

l 创建实例对象:

Ø 通常方式:String str = new String(new StringBuffer("abc"));

Ø 反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));

//调用获得的方法时要用到上面相同类型的实例对象

l Class.newInstance()方法:

Ø 例子:String obj = (String)Class.forName("java.lang.String").newInstance();

Ø 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

Ø 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

 

通过反射实例化对象,一般是通过ConstructornewInstance方法。为了简便,Class类也提供了newInstance方法,但只能调用无参的构造方法。

 

 

Field

l Field类代表某个类中的一个成员变量

l 演示用eclipse自动生成Java类的构造方法

l 问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

l 示例代码:

ReflectPoint point = new ReflectPoint(1,7);

Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");

System.out.println(y.get(point));

//Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");

Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");

x.setAccessible(true);

System.out.println(x.get(point));

 

ClassgetField("变量名")方法得到的是publicfield

要得到privatefield需用getDeclaredField("变量名")方法。

要用私有变量还需设置可获取:

Field fieldX = p1.getClass().getDeclaredField("x");

fieldX.setAccessible(true);

System.out.println(fieldX.get(p1));

 

 

作业:将任意一个对象中所有String类型的成员变量所对应的字符串中的b改为a

private static void changStringValue(Object obj) {

Field[] fields = obj.getClass().getFields();

for(Field field : fields){

if(field.getType() == String.class){

String oldValue = (String)field.get(obj);

String newValue = oldValue.replace("b""a");

field.set(obj, newValue);

}

}

}

 

Method

l Method类代表某个类中的一个成员方法

l 得到类中的某一个方法:

Ø 例子:      Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

l 调用方法:

Ø 通常方式:System.out.println(str.charAt(1));

Ø 反射方式: System.out.println(charAt.invoke(str, 1)); 

• 如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!

l jdk1.4和jdk1.5的invoke方法的区别:

Ø Jdk1.5:public Object invoke(Object obj,Object... args)

Ø Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

 

 

用反射方式执行某个类中的main方法

l 目标:

Ø 不用反射,调用main方法的代码:

TestArguments.main(new String[]{"aaa","bbb","ccc"});

Ø 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?

 

l 问题:

Ø 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

l 解决办法:

Ø mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});

Ø mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

 

String className = args[0];

Method mainMethod = Class.forName(className).getMethod("main",String[].class);

mainMethod.invoke(null, (Object)(new String[]{"111","222","333"}));

 

数组的反射

l 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

l 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

l 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

int [] a1 = new int[]{1,2,3};

Object aObj3 = a1; //对

Object[] aObj3 = a1; //错

l Arrays.asList()方法处理int[]和String[]时的差异。

Arrays.asList(String[]),放回一个List,List中的元素就是原来String[]中的元素。

Arrays.asList(int[],放回一个List,只有一个元素,就是int[]本身。

l Array工具类用于完成对数组的反射操作。

如果传入的对象是数组,则一个个打印数组元素,如果是非数组则直接打印:

private static void printObject(Object obj) {

Class clazz = obj.getClass();

if(clazz.isArray()){

int len = Array.getLength(obj);

for(int i=0;i<len;i++){

System.out.println(Array.get(obj, i));

}

}else{

System.out.println(obj);

}

}

 

 

Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions

int[] a1 = new int[3];

int[] a2 = new int[4];

System.out.println(a1.getClass() == a2.getClass());  //true

 

 

Arrays.asList方法,如果参数是String[],则会提取元素,组成List。如果是int[]这样的基本类型,因为基本类型不是Object,所以组成的List中只有一个元素:int[]

 

 

补充,理解数组:

int[][] a3 = new int[3][4];

String[] a4 = new String[3];

a3是一个数组,数组的每个元素的类型是int[].

a4是一个数组,数组的每个元素的类型是String

 

Hashcode

 

Hashcode只有在实现了哈希算法的数据结构(如HashSet)中才有用。

HashSet 是如何保证元素唯一性的呢?

是通过元素的两个方法,hashCodeequals来完成。

如果元素的hashcode值不同,不会调用equals

如果元素的HashCode值相同,才会判断equals是否为true。当HashCode值和equals都为true时,两个对象认为是同一个对象。

 

 

Collection col = new HashSet();

ReflectPoint p1 = new ReflectPoint(3,3); // ReflectPoint类中基于x,y编写hashcode和equals方法

ReflectPoint p2 = new ReflectPoint(5,5);

col.add(p1);

col.add(p2);

System.out.println(col.size()); //2

p1.y = 6;  //p1的值改变,导致hashcode改变,在原来的HashSet中将无法找到相等的元素,所以无法remove,造成内存泄漏.

col.remove(p1);

System.out.println(col.size());//仍旧是2

 

 

用类加载器加载资源文件

InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("config.properties "); // config.properties位于classPath目录下

InputStream ips 

= ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");

// config.properties位于n.itcast.day1包下

InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties"); //在ReflectTest2的同一包下

InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/resources/resource.properties"); //在ReflectTest2不同的包下

 

 

Properties props = new Properties();

props.load(ips);

ips.close();

String className = props.getProperty("className");

 

//Collection col = (Collection)Class.forName(className).newInstance();

 

 

 

内省(introspector)javabean

l JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

l 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。

Ø setId()的属性名àid

Ø isLast()的属性名àlast

Ø setCPU的属性名是什么?àCPU

Ø getUPS的属性名是什么?àUPS

总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

l 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:

Ø 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!

Ø JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。

 

 

 

通过内省取得javabean的属性值(使用PropertyDescriptor)

private static Object getProperty(Object pt1, String propertyName)

throws IntrospectionException, IllegalAccessException,

InvocationTargetException {

PropertyDescriptor pd1 = new PropertyDescriptor(propertyName,pt1.getClass());

Method methodGetX = pd1.getReadMethod();

Object retVal = methodGetX.invoke(pt1);

return retVal;

}

通过内省设置javabean的属性值

private static void setProperty(Object pt1, String propertyName,

Object value) throws IntrospectionException,

IllegalAccessException, InvocationTargetException {

PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());

Method methodSetX = pd2.getWriteMethod();

methodSetX.invoke(pt1, value);

}

使用内省,得到一个javabean所有的属性及其值:

ReflectPoint point = new ReflectPoint(3,4,"zhangsan",false);

BeanInfo beanInfo = Introspector.getBeanInfo(point.getClass());

PropertyDescriptor[] propDescs = beanInfo.getPropertyDescriptors();

for(PropertyDescriptor propDesc : propDescs){

String propName = propDesc.getName();

Method getMethod = propDesc.getReadMethod();

System.out.println(propName + " : " + getMethod.invoke(point)) ;

}

 

 

BeanUtils工具包的使用

l 演示用eclipse如何加入jar包,先只是引入beanutils包,等程序运行出错后再引入logging包。

l 在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。

Ø get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。

l 用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。

Ø get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。

l 演示去掉JavaBean(ReflectPoint)的public修饰符时,BeanUtils工具包访问javabean属性时出现的问题。

BeanUtils可以操作属性链

BeanUtils.setProperty(pt1, "birthday.time", 111);

BeanUtils可以接受String作为参数值,它会自动转换,PropertyUtils只能根据bean实际的类型来赋值;

BeanUtils.setProperty(pt1, "x", "9");

PropertyUtils.setProperty(pt1, "x", 8);

MapBean有相似的结构,都是键值对,可以用BeanUtils来操作Map

 

注解

l 先通过@SuppressWarnings的应用让大家认识和了解一下注解:

Ø 通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings("deprecation") 

l @Deprecated

Ø 直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。

l @Override

Ø public boolean equals(Reflect other)方法与HashSet结合讲解

l 总结:

Ø 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

Ø 看java.lang包,可看到JDK中提供的最基本的annotation

 

34 continue

 

 

 

 

 

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD,ElementType.TYPE})

public @interface ItcastAnnotation {

String color() default "blue";

String value();

int[] arrayAttr() default {3,4,5};

TrafficLamp lamp() default TrafficLamp.RED;

MetaAnnotation annotationAttr() default @MetaAnnotation("LMN");

}

 

泛型

 

 

 

ArrayList<String> collection1 = new ArrayList<String>();

ArrayList<Integer> collection2 = new ArrayList<Integer>();

System.out.println(collection1.getClass()==collection2.getClass());

输出true。范型信息只提供给编译器使用,在编译好的class文件中已去掉了类型信息。

 

思考题:不会报错。

 

 

 

自定义范型方法:

 

 

 

 

自定义范型类

DAO: Data Access Object

CRUD: Create Retrieve Update Delete

 

 

通过反射获得范型的参数化类型。

注意:通过反射是无法得到一个变量的范型类型的,如Vector<Date> v1 = new Vector<Date>();

 

 

 

 

 

 

public static void applyVector(Vector<Date> v1){

}

Method appMethod = GenericTest.class.getMethod("applyVector", Vector.class);

Type[] paraTypes = appMethod.getGenericParameterTypes();

ParameterizedType paraType = (ParameterizedType)paraTypes[0];

System.out.println(paraType.getRawType());

System.out.println(paraType.getActualTypeArguments()[0]);

ParameterizedType represents a parameterized type such as Collection<String>.

getRawType()输出的是java.util.Vector

getActualTypeArguments()返回的是一个范型数组,因为有时一个基本类型可包含多个范型,如Map<K,V>。

 

类加载器

 

 

类加载器的委托机制

 

面试题:

能不能自己写个类叫java.lang.System,来替换java提供的System类?

不行,由于类加载的委托机制,父加载器会优先查找类,如找到就加载。

 

编写自己的类加载器:

 

实例:编写一个NetworkClassLoader

class NetworkClassLoader extends ClassLoader {

         String host;

         int port;

         public Class findClass(String name) {

             byte[] b = loadClassData(name);

             return defineClass(name, b, 0, b.length);

         }

         private byte[] loadClassData(String name) {

             // load the class data from the connection

              . . .

         }

 }

 

如果在eclipse中遇到问题,看Problems这个View来查找问题。

 

类加载器的一个高级问题:

 

Ext目录是指JDK中的ext目录D:\javaprograms\Java\jdk1.6.0_01\jre\lib\ext。

放在tomcat中的servlet是由tomcat中的类加载器org.apache.catalina.loader.WebappClassLoader 加载的。

其树机构是:

org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

如果把MyServlet打成jar包,放入jdkext目录,则当请求该servlet时,ExtClassLoader可以找到该类,就加载,但在加载过程中又要加载MyServlet的父类HeepServlet,这时,因为ext目录中不存在这个类,ExtClassLoader无法加载,就报错:类无法找到。

 

代理

代理的概念与作用:

 

代理的框架图:

 

Client程序中应该只出现接口。

AOPAspect Oriented Program 面向切面编程

 

 

动态代理技术:

 

 

快捷键:Ctrl+shift+/ : 注释一段代码  Ctrl+shift+\:取消注释

 

JVM动态生成类:

 

创建动态代理需考虑提供的三样东西:

1、类加载器,一般用目标对象的类加载器

2、接口。

3、实现InvocationHandler接口的类

 

创建Collection的动态代理类:

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

 

通过内部定义的InvocationHandler类和动态代理的构造方法生成Collection代理的实例对象:

Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);

class MyInvocationHandler1 implements InvocationHandler{

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

return null;

}

}

Collection collectionProxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());

通过匿名的InvocationHandler类和动态代理的构造方法生成Collection代理的实例对象:

Collection collectionProxy2 = (Collection)constructor.newInstance(new InvocationHandler(){

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

return null;

}

});

通过Proxy.newProxyInstance方法直接生成Collection代理的实例对象:

Collection collectionProxy3 = (Collection)Proxy.newProxyInstance(

Collection.class.getClassLoader(), 

new Class[]{Collection.class}, 

new InvocationHandler(){

ArrayList target = new ArrayList();

public Object invoke(Object proxy,Method method,Object[] args)

throws Throwable {

long beginTime = System.currentTimeMillis();

Object retValue = method.invoke(target, args);

long endTime = System.currentTimeMillis();

System.out.println("Method " + method.getName() +  " use time :" + (endTime-beginTime));

return retValue;

}

});

 

 

注意:对于从Object继承来的方法,只有hashCode, equals 和 toString会分派给代理执行,其他方法由Object执行。详细描述:

An invocation(调用) of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched(分派) to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.

 

 

 

 

 

Collection collectionProxy3 = (Collection)Proxy.newProxyInstance(

Collection.class.getClassLoader(), 

new Class[]{Collection.class}, 

new InvocationHandler(){

ArrayList target = new ArrayList();

@Override

public Object invoke(Object proxy,Method method,Object[] args)

throws Throwable {

long beginTime = System.currentTimeMillis();

Object retValue = method.invoke(target, args);

long endTime = System.currentTimeMillis();

System.out.println("Method " + method.getName() +  " use time :" + (endTime-beginTime));

return retValue;

}

});

上面黄色的代码块就是切面,需要抽出成独立的类。

方法为:

1、定义Advice接口:

public interface Advice {

void beforeMethod(Method method);

void afterMethod(Method method);

}

2、定义实现接口的MyAdvice类:

 

 

 

public class MyAdvice implements Advice {

long beginTime = 0;

@Override

public void afterMethod(Method method) {

System.out.println("结束了!!!!");

long endTime = System.currentTimeMillis();

System.out.println("Method " + method.getName() +  " use time :" + (endTime-beginTime));

}

@Override

public void beforeMethod(Method method) {

System.out.println("开始啦!!!!");

beginTime = System.currentTimeMillis();

}

}

 

 

由此,要创建代理,就需提供目标对象和Advice

Collection collectionProxy3 = (Collection)getProxy(target,new MyAdvice());

private static Object getProxy(final Object target,final Advice advice) {

Object proxy3 = (Object)Proxy.newProxyInstance(

target.getClass().getClassLoader(), 

target.getClass().getInterfaces(), 

new InvocationHandler(){

@Override

public Object invoke(Object proxy,Method method,Object[] args)

throws Throwable {

/*long beginTime = System.currentTimeMillis();

Object retValue = method.invoke(target, args);

long endTime = System.currentTimeMillis();

System.out.println("Method " + method.getName() +  " use time :" + (endTime-beginTime));

return retValue;*/

advice.beforeMethod(method);

Object retValue = method.invoke(target, args);

advice.afterMethod(method);

return retValue;

}

});

return proxy3;

}

 

 

 

加载properties文件的方法:

先用当前类的classproperties文件变为输入流

InputStream inStream = AopFrameworkTest.class.getResourceAsStream("config.properties");

再用Propertiesload.

Properties prop = new Properties();

prop.load(inStream);

 

 

线程

传统线程机制的回顾

l 创建线程的两种传统方式

Ø 在Thread子类覆盖的run方法中编写运行代码

• 涉及一个以往知识点:能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部对Thread.sleep()语句的try…catch处理?

Ø 在传递给Thread对象的Runnable对象的run方法中编写代码

Ø 总结:查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

Ø 问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?

• 涉及到的一个以往知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法。

              答:会执行子类自己覆盖的run方法。因为执行Runnable对象的run方法的行为是Thread 类的run方法定义的,而该方法已被子类覆盖了。

 

使用Thread子类覆盖的run方法创建线程:

Thread thread1 = new Thread(){

@Override

public void run() {

while(true){

System.out.println(Thread.currentThread().getName() + " is running.");

try {

Thread.sleep(500);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

};

thread1.start();

 

使用传递给Thread对象的Runnable对象创建线程

Thread thread2 = new Thread(new Runnable(){

@Override

public void run() {

while(true){

System.out.println(Thread.currentThread().getName() + " is running.");

try {

Thread.sleep(500);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

});

thread2.start();

 

定时器的应用

 

Timer类

TimerTask类

Timer timer = new Timer();

TimerTask task1 = new MyTask("Task1");

timer.schedule(task1, 1000, 5000);

 

class MyTask extends TimerTask{

private String name = null;

public MyTask(String name){

super();

this.name = name;

}

@Override

public void run() {

System.out.println(name + " is running!");

}

}

 

线程的同步

l 使用synchronized代码块

l 使用synchronized方法

l 静态方法所使用的同步监视器对象是类的字节码文件class

 

下面的方法Outputer类的outputString方法,如果没有加上synchronized关键字,结果将导致一个线程没有完成输入a字符串时,另一个线程就开始出出B字符串。

public class TraditionalThreadSynchronized {

 

public static void main(String[] args) {

new TraditionalThreadSynchronized().doJob();

}

public void doJob(){

final Outputer outputer = new Outputer();

new Thread(new Runnable(){

@Override

public void run() {

while(true){

outputer.outputString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

while(true){

outputer.outputString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

class Outputer{

public synchronized void  outputString(String str){

for(int i=0;i<str.length();i++){

System.out.print(str.charAt(i));

}

System.out.println();

}

}

 

}

 

线程的同步互斥的图文解说

 

 

线程间的通信

wait与notify实现线程间的通信

 

编程题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环20,如此循环50次,请写出程序。

public class TraditionalThreadCommuniation {

 

public static void main(String[] args) {

final MyJob job = new MyJob();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=0;i<50;i++){

job.mainJob();

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=0;i<50;i++){

job.subJob();

}

}

}).start();

 

}

 

}

 

class MyJob{

private final int mainLoopTime = 20;

private final int subLoopTime = 10;

private boolean isSubRun = true;

public synchronized void mainJob(){

while(isSubRun){

try {

this.wait();

catch (InterruptedException e) {

e.printStackTrace();

}

}

for(int i=1;i<=mainLoopTime;i++){

System.out.println("main job is running " + i + " times");

}

isSubRun = true;

this.notify();

}

public synchronized void subJob(){

while(!isSubRun){

try {

this.wait();

catch (InterruptedException e) {

e.printStackTrace();

}

}

for(int i=1;i<=subLoopTime;i++){

System.out.println("sub job is running " + i + " times");

}

isSubRun = false;

this.notify();

}

}

 

 

 

 

通过ThreadLocal实现线程范围内的共享数据

l 见下页的示意图和辅助代码解释ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

 

l 每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

l ThreadLocal的应用场景:

Ø 订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。

Ø  银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。

Ø 例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。

l 实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。

public class ThreadScopeShareData {

 

private static ThreadLocal<Integer> data = new ThreadLocal<Integer>();

public static ThreadLocal<Integer> getData() {

return data;

}

 

public static void main(String[] args) {

for(int i =0;i<2;i++){

new Thread(new Runnable(){

@Override

public void run() {

ThreadScopeShareData threadData = new ThreadScopeShareData();

threadData.getData().set(new Random().nextInt());

new A().get();

new B().get();

}

}).start();

}

 

}

 

}

 

class A{

public void get(){

Integer data = ThreadScopeShareData.getData().get();

System.out.println("A from " + Thread.currentThread().getName() 

" get data :" + data);

}

}

 

class B{

public void get(){

Integer data = ThreadScopeShareData.getData().get();

System.out.println("B from " + Thread.currentThread().getName() 

" get data :" + data);

}

}

 

 

l 实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。

Ø 对基本类型的数据的封装,这种应用相对很少见。

Ø 对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

public class ThreadLocalTest {

 

public static void main(String[] args) {

for(int i =0;i<2;i++){

new Thread(new Runnable(){

@Override

public void run() {

MyThreadLocalData threadData = MyThreadLocalData.getInstance();

threadData.setAge(new Random().nextInt(100));

threadData.setName("zhangsan" + new Random().nextInt(100));

new A1().get();

new B1().get();

}

}).start();

}

 

}

 

}

 

 

class MyThreadLocalData{

private MyThreadLocalData(){}

public static MyThreadLocalData getInstance(){

MyThreadLocalData instance = data.get();

if(instance==null){

instance = new MyThreadLocalData();

data.set(instance);

}

return instance;

}

private static ThreadLocal<MyThreadLocalData> data = new ThreadLocal<MyThreadLocalData>();

private int age;

private String name;

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

 

class A1{

public void get(){

int age = MyThreadLocalData.getInstance().getAge();

String name = MyThreadLocalData.getInstance().getName();

System.out.println("A1 from " + Thread.currentThread().getName() 

" get age :" + age + " get name : " + name);

}

}

 

class B1{

public void get(){

int age = MyThreadLocalData.getInstance().getAge();

String name = MyThreadLocalData.getInstance().getName();

System.out.println("B1 from " + Thread.currentThread().getName() 

" get age :" + age + " get name : " + name);

}

}

 

多个线程访问共享对象和数据的方式

l 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。

public class SellTicketDemo {

 

/**

 * @param args

 */

public static void main(String[] args) {

TicketSeller ticketSeller = new TicketSeller();

new Thread(ticketSeller).start();

new Thread(ticketSeller).start();

 

}

 

}

 

class TicketSeller implements Runnable{

private int count = 100;

 

@Override

public void run() {

Thread t = Thread.currentThread();

int n = -1;

while((n=sellTicket())>0){

System.out.println(t.getName() + " sell thicker " + n);

Thread.yield();

}

}

public synchronized int sellTicket(){

if(count-->0){

return count;

}else{

return -1;

}

}

}

 

 

l 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:

1. 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

2. 将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

3. 上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

4. 总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

l 极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

 

设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。

用上面的第1种描述的写法:

public class MultiThreadShareData {

 

public static void main(String[] args) {

ShareData shareData = new ShareData();

new Thread(new IncreaseTask(shareData),"Insease Thread1").start();

new Thread(new IncreaseTask(shareData),"Insease Thread2").start();

new Thread(new DecreaseTask(shareData),"Decrease Thread2").start();

new Thread(new DecreaseTask(shareData),"Decrease Thread2").start();

 

}

 

}

 

class IncreaseTask implements Runnable{

private ShareData shareData = null

public IncreaseTask(ShareData shareData) {

this.shareData = shareData;

}

 

@Override

public void run() {

while(true){

System.out.println(Thread.currentThread().getName() + ": " + shareData.increase());

}

}

}

 

class DecreaseTask implements Runnable{

private ShareData shareData = null

public DecreaseTask(ShareData shareData) {

this.shareData = shareData;

}

 

@Override

public void run() {

while(true){

System.out.println(Thread.currentThread().getName() + ": " + shareData.decrease());

}

}

}

 

class ShareData{

private int num=0;

public synchronized int increase(){

return ++num;

}

public synchronized int decrease(){

return --num;

}

}

 

Java5中的线程并发库

l 看java.util.concurrent包及子包的API帮助文档

l 了解java.util.concurrent.atomic包

Ø 查看atomic包文档页下面的介绍

Ø 通过如下两个方法快速理解atomic包的意义:

• AtomicInteger类的boolean compareAndSet(expectedValue, updateValue); 

• AtomicIntegerArray类的int addAndGet(int i, int delta);

Ø 顺带解释volatile类型的作用,需要查看java语言规范。

l 了解java.util.concurrent.lock包

Ø 在下页通过案例详细讲解

线程池

l 线程池的概念与Executors类的应用

Ø 创建固定大小的线程池

Ø 创建缓存线程池

Ø 创建单一线程池

创建有3个线程的的固定大小的线程池,提交10个任务给这个线程池,同时只会有3个线程执行任务。

public static void main(String[] args) {

ExecutorService threadPool = Executors.newFixedThreadPool(3);

for(int j=0;j<10;j++){

final int tasknum = j;

threadPool.execute(new Runnable(){

@Override

public void run() {

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

System.out.println(Thread.currentThread().getName() + " do task " + tasknum + " " + i);

}

}

});

}

threadPool.shutdown();

}

 

如果用带缓存的线程池,将代码换成

ExecutorService threadPool = Executors.newCachedThreadPool();

提交10个任务后将会同时有10个线程执行。

 

 

l 关闭线程池

Ø shutdown与shutdownNow的比较

 

l 用线程池启动定时器

Ø 调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。

Ø 支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。

ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);

threadPool.scheduleAtFixedRate(new Runnable(){

@Override

public void run() {

System.out.println("ling ling ling ...........");

}

}, 5, 2, TimeUnit.SECONDS);

 

Callable&Future返回任务的结果

l Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。

ExecutorService exec = Executors.newSingleThreadExecutor();

Future<String> future = 

exec.submit(new Callable<String>(){

@Override

public String call() throws Exception {

Thread.sleep(2000);

return "hello";

}

});

exec.shutdown();

System.out.println("等待结果");

try {

System.out.println("拿到结果: " + future.get());

catch (Exception e) {

e.printStackTrace();

}

 

l Callable要采用ExecutorSevice的submit方法提交,返回的future对象可以取消任务。

l CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。

好比我同时种了几块地的麦子,然后就等待收割。收割时,则是那块先成熟了,则先去收割哪块麦子

ExecutorService executor =  Executors.newFixedThreadPool(10);

CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executor); 

for(int i=1;i<=10;i++){

final int seq = i;

completionService.submit(new Callable<Integer>() {

@Override

public Integer call() throws Exception {

Thread.sleep(new Random().nextInt(5000));

return seq;

}

});

}

executor.shutdown();

for(int i=1;i<=10;i++){

try {

System.out.println(completionService.take().get());

catch (InterruptedException e) {

e.printStackTrace();

catch (ExecutionException e) {

e.printStackTrace();

}

}

 

Lock&Condition实现线程同步通信

l Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

下面是用lock改写之前的Outputer类中的synchronized方法outputString()。

注意lock.unlock();要放在finally语句块中。

public class LockTest {

 

public static void main(String[] args) {

new LockTest().doJob();

}

public void doJob(){

final Outputer output = new Outputer();

new Thread(new Runnable(){

@Override

public void run() {

while(true){

output.outputString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

while(true){

output.outputString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

class Outputer{

Lock lock = new ReentrantLock();

public void  outputString(String str){

lock.lock();

try{

for(int i=0;i<str.length();i++){

System.out.print(str.charAt(i));

}

System.out.println();

}finally{

lock.unlock();

}

}

}

 

}

 

l 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

例一:

/**

 * 三个线程不停地读数据,三个线程不停地写数据,可以同时读,但只能一个线程写

 * @author ZhangJie

 *

 */

public class ReadWriteLockTest {

 

public static void main(String[] args) {

final Queue3<Integer> q = new Queue3<Integer>();

//3个写进程

for(int i=0;i<3;i++){

new Thread(new Runnable(){

@Override

public void run() {

while(true){

q.put(new Random().nextInt(1000));

}

}

}).start();

}

//3个读进程

for(int i=0;i<3;i++){

new Thread(new Runnable(){

@Override

public void run() {

while(true){

q.get();

}

}

}).start();

}

}

}

 

class Queue3<T>{

private T data = null;

ReadWriteLock rwlock = new ReentrantReadWriteLock();

public void get(){

try {

rwlock.readLock().lock();   //读数据时加上读锁

System.out.println(Thread.currentThread().getName() + " is ready to get data!");

Thread.sleep((long)(Math.random()*1000));

System.out.println(Thread.currentThread().getName() + "get data: " + data);

catch (InterruptedException e) {

e.printStackTrace();

}finally{

rwlock.readLock().unlock();

}

}

public void put(T data){

try {

rwlock.writeLock().lock();

System.out.println(Thread.currentThread().getName() + " is ready to put data!");

Thread.sleep((long)(Math.random()*1000));

this.data = data;

System.out.println(Thread.currentThread().getName() + "put data: " + data);

catch (InterruptedException e) {

e.printStackTrace();

}finally{

rwlock.writeLock().unlock();

}

}

}

 

 

API中的读写锁的示例:

示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理): 

 class CachedData {

   Object data;

   volatile boolean cacheValid;

   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

 

   void processCachedData() {

     rwl.readLock().lock();

     if (!cacheValid) {

        // Must release read lock before acquiring write lock

        rwl.readLock().unlock();

        rwl.writeLock().lock();

        // Recheck state because another thread might have acquired

        //   write lock and changed state before we did.

        if (!cacheValid) {

          data = ...

          cacheValid = true;

        }

        // Downgrade by acquiring read lock before releasing write lock

        rwl.readLock().lock();

        rwl.writeLock().unlock(); // Unlock write, still hold read

     }

 

     use(data);

     rwl.readLock().unlock();

   }

 }

 

 

读写Collection时使用读写锁

在使用某些种类的 Collection 时,可以使用 ReentrantReadWriteLock 来提高并发性。通常,在预期 collection 很大,读取者线程访问它的次数多于写入者线程,并且 entail 操作的开销高于同步开销时,这很值得一试。例如,以下是一个使用 TreeMap 的类,预期它很大,并且能被同时访问。 

class RWDictionary {

    private final Map<String, Data> m = new TreeMap<String, Data>();

    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    private final Lock r = rwl.readLock();

    private final Lock w = rwl.writeLock();

 

    public Data get(String key) {

        r.lock();

        try { return m.get(key); }

        finally { r.unlock(); }

    }

    public String[] allKeys() {

        r.lock();

        try { return m.keySet().toArray(); }

        finally { r.unlock(); }

    }

    public Data put(String key, Data value) {

        w.lock();

        try { return m.put(key, value); }

        finally { w.unlock(); }

    }

    public void clear() {

        w.lock();

        try { m.clear(); }

        finally { w.unlock(); }

    }

 }

 

 

模拟缓存

public class CacheDemo {

 

private Map<String,Object> catche = new HashMap<String,Object>();

private ReadWriteLock rwLock = new ReentrantReadWriteLock();

public Object getData(String key){

rwLock.readLock().lock();   //读数据加上读锁,与写锁互斥

Object obj;

try {

try {

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

obj = catche.get(key);

if(obj==null){

rwLock.readLock().unlock(); //释放读锁,我认为这步的目的是让其他线程有读取Map中其他key值得机会。

rwLock.writeLock().lock();  //写数据时加上写锁

try {

if(catche.get(key)==null){

try {

Thread.sleep(1000);

catch (InterruptedException e) {

e.printStackTrace();

}

obj = new Object(); //实际应用中从数据库中取值

catche.put(key, obj);

}

finally{

rwLock.writeLock().unlock();

}

rwLock.readLock().lock();

}

finally{

rwLock.readLock().unlock();   //释放读锁

}

return obj;

}

public static void main(String[] args) {

final CacheDemo catche = new CacheDemo();

for(int i=0;i<3;i++){

final String key = "aaa" + i;

new Thread(new Runnable(){

@Override

public void run() {

while(true){

System.out.println(Thread.currentThread().getName() + " get(" + key +")" + "=" + catche.getData(key));

}

}

}).start();

}

 

}

 

}

 

 

l 通过Condition来进行线程通信。可以替代wait()和notify()方法。

l 在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

 

用Condition改写先前的程序:主线程跑100次,子线程跑10次,交替执行50次。

public class ConditionCommunication {

 

 

public static void main(String[] args) {

ConditionCommunication conditionCommunication = new ConditionCommunication();

 

final Business business = conditionCommunication.new Business();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=1;i<10;i++){

business.doMainJob();

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=1;i<10;i++){

business.doSubJob();

}

}

}).start();

}

class Business{

private Lock lock = new ReentrantLock();

private Condition condition = lock.newCondition();

private boolean isSubRun = false;

public void doMainJob(){

lock.lock();

try {

while(isSubRun){

try {

condition.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

for(int i=0;i<100;i++){

System.out.println("Do main job " + i + " times");

}

isSubRun = true;

condition.signal();

finally{

lock.unlock();

}

}

public void doSubJob(){

lock.lock();

try{

while(!isSubRun){

try {

condition.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

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

System.out.println("Do sub job " + i + " times");

}

isSubRun = false;

condition.signal();

}finally{

lock.unlock();

}

}

}

 

}

 

l 一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)

 

例一:可阻塞队列:

public class BoundedBuffer {

private Object[] buffer = new Object[100];

private int takeIndex = 0; //

private int putIndex = 0;  //

private int count=0; //buffer中数量

private Lock lock = new ReentrantLock();

private Condition notFull = lock.newCondition();

private Condition notEmpty = lock.newCondition();

public void put(Object obj){

lock.lock();

try{

while(count==buffer.length){

try {

notFull.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

buffer[putIndex]=obj;

if(++putIndex==buffer.lengthputIndex=0;

count++;

System.out.println(Thread.currentThread().getName() + " put " + obj);

notEmpty.signal();

}finally{

lock.unlock();

}

}

public Object take(){

lock.lock();

try{

while(count==0){

try {

notEmpty.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

Object obj = buffer[takeIndex];

if(++takeIndex==buffer.lengthtakeIndex=0;

count--;

notFull.signal();

System.out.println(Thread.currentThread().getName() + " take " + obj);

return obj;

}finally{

lock.unlock();

}

}

 

public static void main(String[] args) {

final BoundedBuffer buffer = new BoundedBuffer();

for(int i=0;i<3;i++){

new Thread(new Runnable(){

@Override

public void run() {

while(true){

buffer.put(new Random().nextInt(1000));

try {

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

for(int i=0;i<3;i++){

new Thread(new Runnable(){

@Override

public void run() {

while(true){

buffer.take();

try {

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

 

}

}

 

例二:第一个线程跑30次,接着第二个线程跑20次,接着第三个线程跑10次,接着又第一线程跑。如此往复50次。

提示:需要有3condition

public class ThreeConditionCommunication {

 

public static void main(String[] args) {

ThreeConditionCommunication demo = new ThreeConditionCommunication();

final ThreeConditionCommunication.Business business = demo.new Business();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=0;i<50;i++){

business.doJob1();

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=0;i<50;i++){

business.doJob2();

}

}

}).start();

new Thread(new Runnable(){

@Override

public void run() {

for(int i=0;i<50;i++){

business.doJob3();

}

}

}).start();

 

}

class Business{

private Lock lock = new ReentrantLock();

private Condition job1Condition = lock.newCondition();

private Condition job2Condition = lock.newCondition();

private Condition job3Condition = lock.newCondition();

private int jobTurn = 1;

public void doJob1(){

lock.lock();

try {

while(jobTurn!=1){

try {

job1Condition.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

for(int i=0;i<30;i++){

System.out.println("Do doJob1 " + i + " times");

}

jobTurn=2;  //第一个工作做好后交由第二个工作继续

job2Condition.signal();

finally{

lock.unlock();

}

}

public void doJob2(){

lock.lock();

try {

while(jobTurn!=2){

try {

job2Condition.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

for(int i=0;i<20;i++){

System.out.println("Do doJob2 " + i + " times");

}

jobTurn=3;  //第二个工作做好后交由第三个工作继续

job3Condition.signal();

finally{

lock.unlock();

}

}

public void doJob3(){

lock.lock();

try {

while(jobTurn!=3){

try {

job3Condition.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

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

System.out.println("Do doJob3 " + i + " times");

}

jobTurn=1;  //第三个工作做好后交由第一个工作继续

job1Condition.signal();

finally{

lock.unlock();

}

}

}

 

}

 

 

Semaphore(信号量)

Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

Ø Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。

Ø 另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。

l 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

public class SemaphoreTest {

public static void main(String[] args) {

ExecutorService service = Executors.newCachedThreadPool();

final  Semaphore sp = new Semaphore(3);

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

Runnable runnable = new Runnable(){

public void run(){

try {

sp.acquire();

catch (InterruptedException e1) {

e1.printStackTrace();

}

System.out.println("线程" + Thread.currentThread().getName() + 

"进入,当前已有" + (3-sp.availablePermits()) + "个并发");

try {

Thread.sleep((long)(Math.random()*10000));

catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("线程" + Thread.currentThread().getName() + 

"即将离开");

sp.release();

//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元

System.out.println("线程" + Thread.currentThread().getName() + 

"已离开,当前已有" + (3-sp.availablePermits()) + "个并发");

}

};

service.execute(runnable);

 

}

}

}

 

CyclicBarrier同步工具类

讲解CyclicBarrier的功能时,通过辅助画图的方式说明,效果会更好。

\              /

  \     |    /

------------------------三个线程干完各自的任务,在不同的时刻到达集合点后,就可以接着忙各自的工作去了,再到达新的集合点,再去忙各自的工作,

到达集合点了用CyclicBarrier对象的await方法表示。

  /     |   \

/       |     \

-------------------

public class CyclicBarrierTest {

 

public static void main(String[] args) {

ExecutorService service = Executors.newCachedThreadPool();

final  CyclicBarrier cb = new CyclicBarrier(3);

for(int i=0;i<3;i++){

Runnable runnable = new Runnable(){

public void run(){

try {

Thread.sleep((long)(Math.random()*10000));

System.out.println("线程" + Thread.currentThread().getName() + 

"即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));

cb.await();

Thread.sleep((long)(Math.random()*10000));

System.out.println("线程" + Thread.currentThread().getName() + 

"即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));

cb.await();

Thread.sleep((long)(Math.random()*10000));

System.out.println("线程" + Thread.currentThread().getName() + 

"即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));

cb.await();

catch (Exception e) {

e.printStackTrace();

}

}

};

service.execute(runnable);

}

service.shutdown();

}

}

 

CountDownLatch同步工具类

Ø 犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。这直接通过代码来说明CountDownLatch的作用,这样学员的理解效果更直接。

Ø 可以实现一个人(也可以是多个人)等待其他所有人都来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑。用这个功能做百米赛跑的游戏程序不错哦!

public class CountdownLatchTest {

 

public static void main(String[] args) {

ExecutorService service = Executors.newCachedThreadPool();

final CountDownLatch cdOrder = new CountDownLatch(1);

final CountDownLatch cdAnswer = new CountDownLatch(3);

for(int i=0;i<3;i++){

Runnable runnable = new Runnable(){

public void run(){

try {

System.out.println("线程" + Thread.currentThread().getName() + 

"正准备接受命令");

cdOrder.await();

System.out.println("线程" + Thread.currentThread().getName() + 

"已接受命令");

Thread.sleep((long)(Math.random()*10000));

System.out.println("线程" + Thread.currentThread().getName() + 

"回应命令处理结果");

cdAnswer.countDown();

catch (Exception e) {

e.printStackTrace();

}

}

};

service.execute(runnable);

}

try {

Thread.sleep((long)(Math.random()*10000));

System.out.println("线程" + Thread.currentThread().getName() + 

"即将发布命令");

cdOrder.countDown();

System.out.println("线程" + Thread.currentThread().getName() + 

"已发送命令,正在等待结果");

cdAnswer.await();

System.out.println("线程" + Thread.currentThread().getName() + 

"已收到所有响应结果");

catch (Exception e) {

e.printStackTrace();

}

service.shutdown();

 

}

}

 

Exchanger同步工具类

Ø 用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。

 

public class ExchangerTest {

 

public static void main(String[] args) {

ExecutorService service = Executors.newCachedThreadPool();

final Exchanger<String> exchanger = new Exchanger<String>();

service.execute(new Runnable(){

public void run() {

try {

 

String data1 = "zxx";

System.out.println("线程" + Thread.currentThread().getName() + 

"正在把数据" + data1 +"换出去");

Thread.sleep((long)(Math.random()*10000));

String data2 = (String)exchanger.exchange(data1);

System.out.println("线程" + Thread.currentThread().getName() + 

"换回的数据为" + data2);

}catch(Exception e){

}

}

});

service.execute(new Runnable(){

public void run() {

try {

 

String data1 = "lhm";

System.out.println("线程" + Thread.currentThread().getName() + 

"正在把数据" + data1 +"换出去");

Thread.sleep((long)(Math.random()*10000));

String data2 = (String)exchanger.exchange(data1);

System.out.println("线程" + Thread.currentThread().getName() + 

"换回的数据为" + data2);

}catch(Exception e){

}

}

});

service.shutdown();

}

}

 

可阻塞的队列(BlockingDeque)

l 什么是可阻塞队列,阻塞队列的作用与实际应用,阻塞队列的实现原理。

l 阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。

l BlockingQueue

Ø 只有put方法和take方法才具有阻塞功能

l 用3个空间的队列来演示阻塞队列的功能和效果。

示例代码:两个线程往队列里放数据,一个线程从队列里取数据。

public class BlockingQueueTest {

public static void main(String[] args) {

final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);

for(int i=0;i<2;i++){

new Thread(){

public void run(){

while(true){

try {

Thread.sleep((long)(Math.random()*1000));

System.out.println(Thread.currentThread().getName() + "准备放数据!");

queue.put(1);

System.out.println(Thread.currentThread().getName() + "已经放了数据," + 

"队列目前有" + queue.size() + "个数据");

catch (InterruptedException e) {

e.printStackTrace();

}

 

}

}

}.start();

}

new Thread(){

public void run(){

while(true){

try {

//将此处的睡眠时间分别改为1001000,观察运行结果

Thread.sleep(1000);

System.out.println(Thread.currentThread().getName() + "准备取数据!");

queue.take();

System.out.println(Thread.currentThread().getName() + "已经取走数据," + 

"队列目前有" + queue.size() + "个数据");

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}.start();

}

}

 

 

 

 

同步集合

l 传统集合类在并发访问时是有问题的,也就是说是线程不安全的。

l 传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码。

Collections.synchronizedMap(Map<K,V> m)返回的是一个SynchronizedMap。

这个类是位于Collections中的内部类,其运用了代理模式,给底层的Map加上synchronized。

l 传统方式下的Collection在迭代集合时,不允许对集合进行修改。

Ø 用空中网面试的同步级线程题进行演示

Ø 根据AbstractList的checkForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。

下面这段代码将抛出ConcurrentModificationException异常:

public class CollectionModifyExceptionTest {

public static void main(String[] args) {

Collection users = new ArrayList();//new CopyOnWriteArrayList();

users.add(new User("张三",28));

users.add(new User("李四",25));

users.add(new User("王五",31));

Iterator itrUsers = users.iterator();

while(itrUsers.hasNext()){

System.out.println("aaaa");

User user = (User)itrUsers.next();

if("张三".equals(user.getName())){

users.remove(user);

else {

System.out.println(user);

}

}

}

}

如果删除的是李四,则不会抛异常,但只会输出张三,王五没有输出。

如果删除的是王五,,输出张三,李四后,会抛异常。

所以传统集合中不能用Iterator一边遍历,一边对集合进行操作。

只要把集合从ArrayList改为CopyOnWriteArrayList就能解决这个问题。

 

l Java5中提供了如下一些同步集合类:

Ø 通过看java.util.concurrent包下的介绍可以知道有哪些并发集合

Ø ConcurrentHashMap

Ø CopyOnWriteArrayList

Ø CopyOnWriteArraySet

 

面试题

 

1

现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:

package read;

public class Test {

public static void main(String[] args){

        

System.out.println("begin:"+(System.currentTimeMillis()/1000));

/*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。

修改程序代码,开四个线程让这16个对象在4秒钟打完。

*/

for(int i=0;i<16;i++){  //这行代码不能改动

final String log = ""+(i+1);//这行代码不能改动

{

      Test.parseLog(log);

}

}

}

//parseLog方法内部的代码不能改动

public static void parseLog(String log){

System.out.println(log+":"+(System.currentTimeMillis()/1000));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

思考: 只能修改for循环中的Test.parseLog(log);这条语句,但不能在这里建线程,因为在for循环中建的话,就变成了建16个线程了。

这样就只能在循环外建线程,但如何把需要输出的log传给线程呢?这就要通过个中间媒介来存储和获取数据,这个中间媒介就是BlockingQueue 。

于是,log在for循环中生成,放入BlockingQueue,如果队列已满,将会阻塞。起4个线程从BlockingQueue中取log,并输出。如果BlockingQueue已空,则阻塞。

public class Test {

 

public static void main(String[] args) {

final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);

 

for (int i = 0; i < 4; i++) {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

String data = queue.take();

Test.parseLog(data);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

 

System.out.println("begin:" + (System.currentTimeMillis() / 1000));

 

/*

 * 模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。

 * 修改程序代码,开四个线程让这16个对象在4秒钟打完。

 */

for (int i = 0; i < 16; i++) { // 这行代码不能改动

final String log = "" + (i + 1);// 这行代码不能改动

{

try {

queue.put(log);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

}

 

// parseLog方法内部的代码不能改动

public static void parseLog(String log) {

System.out.println(log + ":" + (System.currentTimeMillis() / 1000));

 

try {

Thread.sleep(1000);

catch (InterruptedException e) {

e.printStackTrace();

}

}

 

}

 

2

现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的。原始代码如下:

package queue;

public class Test {

public static void main(String[] args) {

System.out.println("begin:"+(System.currentTimeMillis()/1000));

for(int i=0;i<10;i++){  //这行不能改动

String input = i+"";  //这行不能改动

String output = TestDo.doSome(input);

System.out.println(Thread.currentThread().getName()+ ":" + output);

}

}

}

//不能改动此TestDo

class TestDo {

public static String doSome(String input){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

String output = input + ":"+ (System.currentTimeMillis() / 1000);

return output;

}

}

思考:与题1一样,也是需要队列来作为中间媒介传递数据。

不同之处在于,要达到“只有上一个消费者消费完后,下一个消费者才能消费数据”这个需求。就要在线程的run方法中使用lock实现串行化。

public class Test {

public static void main(String[] args) {

final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);

//final SynchronousQueue<String> queue = new SynchronousQueue<String>(true);

final Lock lock = new ReentrantLock();

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

new Thread(new Runnable() {

@Override

public void run() {

lock.lock();

try {

String input = queue.take();

String output = TestDo.doSome(input);

System.out.println(Thread.currentThread().getName()+ ":" + output);

catch (Exception e) {

e.printStackTrace();

}finally{

lock.unlock();

}

}

}).start();

}

System.out.println("begin:"+(System.currentTimeMillis()/1000));

for(int i=0;i<10;i++){  //这行不能改动

String input = i+"";  //这行不能改动

try {

queue.put(input);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

//不能改动此TestDo

class TestDo {

public static String doSome(String input){

try {

Thread.sleep(1000);

catch (InterruptedException e) {

e.printStackTrace();

}

String output = input + ":"+ (System.currentTimeMillis() / 1000);

return output;

}

}

 

3

现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:

4:4:1258199615

1:1:1258199615

3:3:1258199615

1:2:1258199615

        请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚1秒输出结果,如下所示:

4:4:1258199615

1:1:1258199615

3:3:1258199615

1:2:1258199616

  总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:

package syn;

 

//不能改动此Test

public class Test extends Thread{

private TestDo testDo;

private String key;

private String value;

public Test(String key,String key2,String value){

this.testDo = TestDo.getInstance();

/*常量"1""1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,

以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/

this.key = key+key2; 

this.value = value;

}

 

 

public static void main(String[] args) throws InterruptedException{

Test a = new Test("1","","1");

Test b = new Test("1","","2");

Test c = new Test("3","","3");

Test d = new Test("4","","4");

System.out.println("begin:"+(System.currentTimeMillis()/1000));

a.start();

b.start();

c.start();

d.start();

 

}

public void run(){

testDo.doSome(key, value);

}

}

 

class TestDo {

 

private TestDo() {}

private static TestDo _instance = new TestDo();

public static TestDo getInstance() {

return _instance;

}

 

public void doSome(Object key, String value) {

// 以大括号内的是需要局部同步的代码,不能改动!

{

try {

Thread.sleep(1000);

System.out.println(key+":"+value + ":"

+ (System.currentTimeMillis() / 1000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

这里的难点是如果找到同步代码块同步的对象。参数key,不是同一对象,所以用key是无法同步的。只能通过一种集合类,把key保存到集合中,且相互equal的key,保存的是同一对象。

解决办法是用的CopyOnWriteArrayList。

public class Test extends Thread{

private TestDo testDo;

private String key;

private String value;

public Test(String key,String key2,String value){

this.testDo = TestDo.getInstance();

/*常量"1""1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,

以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/

this.key = key+key2; 

this.value = value;

}

 

 

public static void main(String[] args) throws InterruptedException{

Test a = new Test("1","","1");

Test b = new Test("1","","2");

Test c = new Test("3","","3");

Test d = new Test("4","","4");

System.out.println("begin:"+(System.currentTimeMillis()/1000));

a.start();

b.start();

c.start();

d.start();

 

}

public void run(){

testDo.doSome(keyvalue);

}

}

 

class TestDo {

 

private TestDo() {}

private static TestDo _instance = new TestDo();

public static TestDo getInstance() {

return _instance;

}

private CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<Object>();

public void doSome(Object key, String value) {

// 以大括号内的是需要局部同步的代码,不能改动!

Object obj = key;

if(!list.contains(key)){

list.add(key);

}else{

for(Iterator<Object> iter=list.iterator();iter.hasNext();){

Object o = iter.next();

if(obj.equals(o)){

obj = o;

break;

}

}

}

synchronized(obj)

{

try {

Thread.sleep(1000);

System.out.println(key+":"+value + ":"

+ (System.currentTimeMillis() / 1000));

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

}

 

 

 

 

 

0 0
原创粉丝点击