枚举:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象
枚举可以让编译器在编译时就可以控制源程序中填写的非法值
枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法
枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔
把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误
枚举只有一个成员时,就可以作为一种单例的实现方式
class Test{
public static void main(String[] args){
WeekDay w=WeekDay.SUN; //WeekDay所能取得的值只能是类中所定义的那几个常量
w.print();
}
}
//模拟枚举
abstract class WeekDay{
//私有化构造函数,无法实例化
private WeekDay(){}
//利用匿名内部类来创建对象,匿名内部类是唯一一种无构造方法的类
public static final WeekDay SUN=new WeekDay(){ //注意:这是在WeekDay类的内部来创建内部类,private是不起作用的
//private只能说相对于不是We ekDay类的外部来创建时才起作用
//覆盖抽象方法
public void print(){
System.out.println("今天是星期天,明天是星期一");
}
};
public static final WeekDay MON=new WeekDay(){
public void print(){
System.out.println("今天是星期一,明天星期二");
}
};
public static final WeekDay TUE=new WeekDay(){
public void print(){
System.out.println("今天是星期二,明天星期三");
}
};
public static final WeekDay WED=new WeekDay(){
public void print(){
System.out.println("今天是星期三,明天星期四");
}
};
public static final WeekDay THI=new WeekDay(){
public void print(){
System.out.println("今天是星期四,明天星期五");
}
};
public static final WeekDay FRI=new WeekDay(){
public void print(){
System.out.println("今天是星期五,明天星期六");
}
};
public static final WeekDay SAT=new WeekDay(){
public void print(){
System.out.println("今天是星期六,明天是星期日");
}
};
//定义抽象方法
public abstract void print();
}
//枚举练习
class EnumTest{
public static void main(String[] args){
WeekDay wd=WeekDay.SUN;
System.out.println(wd.nextDay());
}
}
//定义枚举,枚举相于一个类
enum WeekDay{
//枚举中的元素位于枚举中的开始的地方,否则编译不通过
SUN{//实现抽象方法,相当于创建了一个内部类
public String nextDay(){
return "monday";
}
},MON{
public String nextDay(){
return "tuesday";
}
},TUE{
public String nextDay(){
return "wednesday";
}
},WEN{
public String nextDay(){
return "thursday";
}
},THU{
public String nextDay(){
return "friday";
}
},FRI{
public String nextDay(){
return "saturday";
}
},SAT{
public String nextDay(){
return "sunday";
}
};
//定义枚举构造函数
WeekDay(){
System.out.println("枚举的构造函数");
}
//定义方法
public abstract String nextDay();
}
可变参数:一个方法接受的参数个数不固定
特点:
只能出现在参数列表的最后;
...位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数
增强for循环:
语法:for ( type 变量名:集合变量名 ) { … }
注意事项:
迭代变量必须在( )中定义
集合变量可以是数组或实现了Iterable接口的集合类
class EnhanceFor{
public static void main(String[] args){
int sum=add(2,3,4,5);
System.out.println("sum="+sum);
}
//可变参数位于参数列表的最后面
public static int add(int x,int...args){
int sum=x;
//参数存储于一个数组中
for(int i:args){//集合变量可以是数组或实现了Itreator接口的集合类
sum=sum+i;
}
return sum;
}
}
反射技术
反射就是把Java类中的各种成分映射成相应的java类
一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等
信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息
这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等
常用方法:
static Class> forName(String className):返回与带有给定字符串名的类或接口相关联的 Class 对象
Constructor
Constructor>[] getConstructors():返回所有公共的构造函数
Method getMethod(String name, Class>... parameterTypes):返回某个公共方法
Method[] getMethods():返回所有公共方法
Field getField(String name):返回某个公共的字段
Field[] getFields():返回所有的公共字段
String getName():以String 的形式返回此 Class 对象所表示的实体名称
T newInstance(): 创建此 Class 对象所表示的类的一个新实例
import java.lang.reflect.*;
class Person{
private String name;
public int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
private void getName(){
System.out.println("name="+name);
}
public void print(){
System.out.println("name="+name+" age="+age);
}
}
class ReflectTest{
public static void main(String[] args) throws Exception{
Person p=new Person("lidaofu",24);
//得到class文件的第一种方式
//Class c=p.getClass();
//得到class文件的第二种方式
//Class c2=Class.forName("Person");
/*参数中要是类的全名(包名+类名)
//Class s=Class.forName("java.lang.String");
//返回类的公共构造函数(即被public所修饰的类)
Constructor[] constructor=c2.getConstructors();
for(int i=0;i
数组的反射:
1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,
既可以当做Object类型使用,又可以当做Object[]类型使用
class Test{
public static void print(Integer[] arr){
for(Integer i:arr)
System.out.println("----"+i);
}
public static void print(int[] arg){
for(int i:arg)
System.out.println("===="+i);
}
}
class ReflectTest{
public static void main(String[] args)throws Exception{
/* Integer[] inr=new Integer[1];
//代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
Class claxx=inr.getClass().getSuperclass();
System.out.println(claxx.getName()); */
Method method=Test.class.getMethod("print",Integer[].class);
/*这里数组要转换为object,因为在jdk1.4中invoke(Object,Object[]),基本数据类型的
数组可以当Object来使用,而非基本数据类型的数组即可以当Object来用又可以当Object[]来
使用,而jdk1.4会把数组打散成若干个参数传递给Object[],这时就会出现参数类型不同的问题
*/
method.invoke(null,(Object)new Integer[]{4,6,1,8,9});
Method method2=Test.class.getMethod("print",int[].class);
//这里是基本数据类型的数组,会被当成一个Object对象来处理,所以不用转换为Object
method2.invoke(null,new int[]{4,7,1,8});
}
}
Annotation(注解)是JDK5.0及以后版本引入的一个特性。注解是java的一个新的类型(与接口很相似),它与类、接口、枚举是在同一个层次,它们都称作
为java的一个类型(TYPE)它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
它的作用非常的多,例如进行编译检查、生成说明文档、代码分析等
JDK提供的几个基本注解
1.@SuppressWarnings
该注解的作用是阻止编译器发出某些警告信息。
它可以有以下参数:
deprecation:过时的类或方法警告
unchecked:执行了未检查的转换时警告。
fallthrough:当Switch程序块直接通往下一种情况而没有Break时的警告
path:在类路径、源文件路径等中有不存在的路径时的警告。
serial:当在可序列化的类上缺少serialVersionUID定义时的警告。
finally:任何finally子句不能完成时的警告。
all:关于以上所有情况的警告
2.@Deprecated
该注解的作用是标记某个过时的类或方法
3.@Override
该注解用在方法前面,用来标识该方法是重写父类的某个方法
元注解 :用来修饰注解的注解
1.@Retention
它是被定义在一个注解类的前面,用来说明该注解的生命周期。
它有以下参数:
RetentionPolicy.SOURCE:指定注解只保留在一个源文件当中。
RetentionPolicy.CLASS:指定注解只保留在一个class文件中。
RetentionPolicy.RUNTIME:指定注解可以保留在程序运行期间。
2.@Target
它是被定义在一个注解类的前面,用来说明该注解可以被声明在哪些元素前。
它有以下参数:
ElementType.TYPE:说明该注解只能被声明在一个类前。
ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
ElementType.PACKAGE:说明该注解只能声明在一个包名前。
注解的生命周期
一个注解可以有三个生命周期,它默认的生命周期是保留在一个CLASS文件,但它也可以由一个@Retetion的元注解指定它的生命周期。
1.java源文件
当在一个注解类前定义了一个@Retetion(RetentionPolicy.SOURCE)的注解,
那么说明该注解只保留在一个源文件当中,当编译器将源文件编译成class文件时,它不会
将源文件中定义的注解保留在class文件中。
2.class文件中
当在一个注解类前定义了一个@Retetion(RetentionPolicy.CLASS)的注解,那么说明该注解只保留在一个class文件当中,
当加载class文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问
3.程序运行期间
当在一个注解类前定义了一个@Retetion(RetentionPolicy.RUNTIME)的注解,那么说明该注解在程序运行期间都会存
在内存当中。此时,我们可以通过反射来获取定义在某个类上的所有注解
注解的定义:
一个简单的注解:
public @interface Annotation01{
//
定义公共的final静态属性
....
//
定以公共的抽象方法
......
}
1.注解可以有哪些成员
注解和接口相似,它只能定义final静态属性和公共抽象方法
2.注解的方法
方法前默认会加上 public abstract
在定义方法时可以定义方法的默认返回值
注解的使用
注解的使用分为三个过程:定义注解-->声明注解-->得到注解
1.定义注解
2.声明注解
1.在哪些元素上声明注解
如果定义注解时没有指定@Target元注解来限制它的使用范围,那么该注解可以使用在ElementType枚举
指定的任何一个元素前。否则,只能声明在@Target元注解指定的元素前一般形式:@注解名()
2.对注解的方法的返回值进行赋值
对于注解中定义的每一个没有默认返回值的方法,在声明注解时必须对它的每一个方法的返回值进行赋值
一般形式:@注解名(方法名=方法返回值...)
如果方法返回的是一个数组时,那么将方法返回值写在{}符号里:@注解名(方法名={返回值1,返回值2,...})
对于只含有value方法的注解,在声明注解时可以只写返回值
注意:注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,
非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。
这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,
并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,
一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法
3.得到注解
对于生命周期为运行期间的注解,都可以通过反射获得该元素上的注解实例
1声明在一个类中的注解
可以通过该类Class对象的getAnnotation或getAnnotations方法获得
2.声明在字段上的注解
可以通过Field对象的getAnnotation或getAnnotations方法获得
3.声明在方法上的注解
可以通过Method对象的getAnnotation或getAnnotations方法获得
总结
注解可以看成是一个接口,注解实例就是一个实现了该接口的动态代理类。
注解大多是用做对某个类、方法、字段进行说明,标识的。以便在程序运行期间我们通
过反射获得该字段或方法的注解的实例,来决定该做些什么处理或不该进行什么处理
/定义一个用在字段上的注解
package li.dao.fu.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//说明注解能定义的位置
@Target(ElementType.FIELD)
public @interface AnnotationField {
String name() default "字段属性1";
//如果是方法是value,在定义时可以不写返回值 ,但在使用注解时要写上返回值
String value() ;
//如果方法返回值是数组,那可以将方法返回值写在{}符号里:@注解名(方法名={返回值1,返回值2,...})
String [] array();
}
//定义一个用在方法上的注解
package li.dao.fu.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AnnotationMethod {
String name() default "方法";
}
//定义一个用在类上的注解
package li.dao.fu.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnotationClass {
String name() default "类";
}
//测试注解
package li.dao.fu.test.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@AnnotationClass(name="类定义")
public class AnnotationTest {
//使用注解
//如果方法返回值是数组,那可以将方法返回值写在{}符号里:@注解名(方法名={返回值1,返回值2,...})
@AnnotationField(value="字段属性2",array={"这是一个","定义在字段上的","注解"})
public String str;
AnnotationTest(String name){
this.str=name;
}
@AnnotationMethod(name="方法定义")
public void out(){
System.out.println(str+" my name is lidaofu");
}
public static void main(String[] args) throws Exception{
AnnotationTest at=new AnnotationTest("hello");
at.out();
//通过反射获得类上的注解
AnnotationClass annotation=AnnotationTest.class.getAnnotation(AnnotationClass.class);
System.out.println(annotation.name());
//获得方法上的注解
Method method=AnnotationTest.class.getMethod("out");
AnnotationMethod annotation2=method.getAnnotation(AnnotationMethod.class);
System.out.println(annotation2.name());
//获得字段上的注解
Field[] fields=AnnotationTest.class.getFields();
for(Field field:fields){
AnnotationField annotation3=field.getAnnotation(AnnotationField.class);
System.out.println(annotation3.name());
System.out.println(annotation3.value());
System.out.println(annotation3.array().length);
}
}
}
代理
AOP:
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术
静态代理:
//定义接口
public interface Work {
public void doWork();
public void getName();
}
//实现接口
public class Student implements Work {
@Override
public void doWork() {
System.out.println("study study");
}
@Override
public void getName() {
System.out.println("Student");
}
}
//定义一个静态代理类
public class StaticProxy implements Work {
private Work work;
StaticProxy(Work work){
this.work=work;
}
//其实就是方法的回调
@Override
public void doWork() {
this.work.doWork();
}
@Override
public void getName() {
this.work.getName();
}
}
//测试
public class ProxyTest {
public static void main(String[] args) throws Exception {
Student stu=new Student();
StaticProxy sp=new StaticProxy(stu);
sp.doWork();
sp.getName();
}
}
当如果接口加一个方法,所有的实现类和代理类里都需要做个实现,这就增加了代码的复杂度
而为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情
而动态代理可以解决这一问题
动态代理:
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理
java动态代理步骤:
1.通过实现InvocationHandler接口创建自己调用处理器
2.实现代理类
3.绑定被代理类对象,使用Proxy类的newProxyInstance函数
创建动态代理类实例
4.重写调用处理器(invoke函数)的代码
//定义接口
package li.dao.fu.test.proxy2;
public interface Hello {
public void sayHello();
public void sayGoodBye();
}
//定义接口实现类
package li.dao.fu.test.proxy2;
public class HelloImp implements Hello {
@Override
public void sayHello() {
System.out.println("hello,my name is lidaofu");
}
@Override
public void sayGoodBye() {
System.out.println("goodbye ,lidaofu");
}
}
//实现InvocationHandler方法
package li.dao.fu.test.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
//目标对象,也可以说是实例绑定对象
private Object obj=null;
public MyInvocationHandler() {
super();
}
public MyInvocationHandler(Object obj) {
super();
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("sayHello")){
System.out.println("sayHello()方法被拦截");
return null;
}
return method.invoke(obj, args);
}
}
//测试类
package li.dao.fu.test.proxy2;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
/*
Proxy.newInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)的三个参数分析
第一个参数:一个类加载器
第二个参数:所要代理的所有接口的class,如果只有一个接口可以写成:实现接口类.class.getInterfaces();
也可以写成Class[]{接口.class}
第三个参数:一个实现了InvocationHandler类的实例,是代理实例的调用处理程序 实现的接口
每个代理实例都具有一个关联的调用处理程序,当代理实例调用方法时会首先调用这个接口中的invock方法
*/
Hello hl=(Hello)Proxy.newProxyInstance(Hello.class.getClassLoader(), HelloImp.class.getInterfaces(), new MyInvocationHandler(new HelloImp()));
hl.sayHello();
hl.sayGoodBye();
}
}
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类
//读取配置文件中的信息,运行配置文件中提供的文件名
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
class ReflectTest{
public static void main(String[] args) throws Exception{
//第一种读取配置文件的方式
//InputStream in=new FileInputStream("properties.txt");
//第二种读取配置文件的方式
//Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件
//直接使用类加载器时,不能以/打头
//InputStream in=ReflectTest.class.getClassLoader().getResourceAsStream("temp/properties.txt");
//Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件
InputStream in=ReflectTest.class.getResourceAsStream("temp\\properties.txt");
Properties pro=new Properties();
pro.load(in);
in.close();
String name=pro.getProperty("name");
Collection con=(Collection)Class.forName(name).newInstance();
con.add("lidaofu");
con.add("daofuli");
con.add("iloveyou");
System.out.println(con);
}
}
内省与JavaBean:
内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作,JavaBean是一种特殊的Java类
主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则
如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用
命名规则:
去掉set前缀,剩余部分就是属性名
如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的
如:
setId()-->id
isLast()-->last
setCPU-->CPU
getUPS-->UPS
JavaBean是一种特殊的Java类,
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省
import java.beans.*;
import java.lang.reflect.*;
class Person{
private String name;
Person(String name){
this.name=name;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
}
class InstroSpectorTest{
public static void main(String[] args) throws Exception{
Person p=new Person("lidaofu");
String getName="name";
//getProperty(getName,p);
getBeanInfo(getName,p);
}
//第一种方式
//通过java.beans.PropertyDescriptor来访问get,set方法
public static void getProperty(String getName,Person p) throws Exception{
//创建一个PropertyDescriptor
PropertyDescriptor pd=new PropertyDescriptor(getName,p.getClass());
//获取读取的方法
Method method=pd.getReadMethod();
//取值
String name=(String)method.invoke(p);
System.out.println(name);
//获取写方法
Method method2=pd.getWriteMethod();
//写入值
method2.invoke(p,"iloveyou");
System.out.println(p.getName());
}
//第二种方式
//通过java.beans.BeanInfo来访问get,set方法
public static void getBeanInfo(String getName,Person p) throws Exception{
//获取对应的BeanInfo
BeanInfo info=Introspector.getBeanInfo(p.getClass());
//获取PropertyDescriptor
PropertyDescriptor[] pds=info.getPropertyDescriptors();
//循环获取所需的PropertyDescriptor
for(PropertyDescriptor pd:pds){
if(pd.getName().equals(getName)){
Method method=pd.getReadMethod();
String name=(String)method.invoke(p);
System.out.println(name);
Method method2=pd.getWriteMethod();
method2.invoke(p,"iloveyou");
System.out.println(p.getName());
break;
}
}
}
//第三种方式:使用第三方提供的工具包,如:BeanUtils
}
定时器的应用:
Timer类:定时器类
TimerTask类:创建定时器任务的类
package li.dao.fu.test.thread;
/*
* 定时器的运用
*
* */
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
/*class Task extends TimerTask{
public void run(){
while(true)
System.out.println("this is task");
}
}*/
public class TimerTest {
public static void main(String[] args) {
//定时器,创建一个定时器,接收一个任务,让任务在延迟多长时间后每隔固定时间接着执行
/*
* 其实Timer就是一个多线程,它在后台控制着任务的执行情况
* Timer所接收的TimerTask是一个实现Runnable的类,run方法 中是我们想要执行的任务
* */
new Timer().schedule(
new TimerTask(){
int i=0;
public void run(){
long time=System.currentTimeMillis();
SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(time));
System.out.println("this is task"+i++);
}
},
2000,
5000);
}
}
多个线程访问共享对象和数据的方式:
如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。
如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:
1.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配
到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
2.将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也
分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
3.上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,
对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之
间的同步互斥和通信。
极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。
package li.dao.fu.test.thread;
//将共享数据封装在一个对象中,然后将这个对象逐一传递给各个Runnable对象
class Ticket{
private int ticket=100;
public synchronized void sell(){
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"卖出第:"+ticket--);
}
}
class TicketThread implements Runnable{
private Ticket ticket=null;
TicketThread(Ticket ticket){
this.ticket=ticket;
}
public void run(){
while(true)
ticket.sell();
}
}
public class SellTicket {
public static void main(String[] args) {
Ticket tk=new Ticket();
TicketThread tt=new TicketThread(tk);
for(int i=0;i<5;i++)
new Thread(tt).start();
}
}
package li.dao.fu.test.thread;
/*
* 练习:主线程运行100次,子线程运行10次,主子线程这样交替运行50次
* */
class SubMain{
private boolean flag=false;
public synchronized void sub(int j){
if(flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++)
System.out.println("sub==="+j+"----"+i);
flag=true;
this.notifyAll();
}
public synchronized void main(int j){
if(!flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<100;i++)
System.out.println("main==="+j+"-----"+i);
flag=false;
this.notifyAll();
}
}
public class TestSubMain {
public static void main(String[] args) {
//定义在局部变量上的内部类在访问局部变量时,局部变量必须被final所修饰
final SubMain sm=new SubMain();
//创建线程
new Thread(new Runnable(){
public void run(){
for(int i=0;i<50;i++)
sm.main(i);
}
}).start();
//创建线程
new Thread(new Runnable(){
public void run(){
for(int i=0;i<50;i++)
sm.sub(i);
}
}).start();
}
}
ThreadLocal实现线程范围的共享变量:
ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。
ThreadLocal的应用场景:
订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。
实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。
实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。
对基本类型的数据的封装,这种应用相对很少见。
对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。
总结:一个ThreadLocal代表一个变量,故其中里只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。如果有一个百个变量要线程共享呢?那请先定义一个对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。
package li.dao.fu.test.thread;
import java.util.Random;
class GetValueA{
ThreadLocal local=null;
GetValueA(ThreadLocal local){
this.local=local;
}
public void get(){
System.out.println(Thread.currentThread().getName()+" A : "+local.get());
}
}
class GetValueB{
ThreadLocal local=null;
GetValueB(ThreadLocal local){
this.local=local;
}
public void get(){
System.out.println(Thread.currentThread().getName()+" B : "+local.get());
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
final ThreadLocal local=new ThreadLocal();
for (int i = 0; i < 5; i++) {
new Thread() {
public void run() {
int date = new Random().nextInt(100);
System.out.println(Thread.currentThread().getName()+" the num="+date);
local.set(date);
new GetValueA(local).get();
new GetValueB(local).get();
}
}.start();
}
}
}
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,
它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,这就是封装。记住,任务是提交给整个线程池,
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
package li.dao.fu.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadExecutors {
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池
//ExecutorService service=Executors.newFixedThreadPool(3);
//创建一个缓冲线程池,线程池中的线程个数是可变的
//ExecutorService service=Executors.newCachedThreadPool();
//创建只含有单个线程的线程池,如果向其中加入的不只一个线程,则会在这个线程执行完后再接着执行后面的线程
//相当于一个线程死亡后再次执行一个新的线程,如果不希望执行后面的线程,则要shutdownNow()
ExecutorService service=Executors.newSingleThreadExecutor();
Runnable runable=null;
for(int i=0;i<5;i++){
runable=new Runnable(){
public void run(){
for(int j=0;j<10;j++)
System.out.println(Thread.currentThread().getName()+"线程在运行第"+j+"次");
}
};
//执行线程任务
service.execute(runable);
}
//线程池中的所有线程执行完毕后线程关闭
service.shutdown();
//线程池中指定的线程执行完后就关闭,不管线程池中是否还有其它的线程
//service.shutdownNow();
}
}
用线程池启动定时器:
调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式
package li.dao.fu.test.thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadSchedule {
public static void main(String[] args) {
ScheduledExecutorService ses=Executors.newSingleThreadScheduledExecutor();
//5秒后执行方法
ses.schedule(new Runnable(){
public void run(){
System.out.println("bombing");
}
},
5,
TimeUnit.SECONDS);
//2秒后执行方法,再每隔4秒执行方法,重复执行
ses.scheduleAtFixedRate(
new Runnable(){
public void run(){
System.out.println("bombing bombing");
}
},
2,
4,
TimeUnit.SECONDS);
}
}
Lock&Condition实现线程同步通信:
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。
两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,
你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,
只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,
这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。
某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,
从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,
要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,
一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
package li.dao.fu.test.thread;
//练习JDK1.5 锁的新特性
//创建四个线程,二个线程打印字符串 "lidaofu",二个线程打印字符串"李道福" 交替打印10次
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource {
private boolean flag=false;
//创建一个锁
Lock lock=new ReentrantLock();
//一个锁可以对应多个Condition
Condition cona=lock.newCondition();
Condition conb=lock.newCondition();
public void outA(int i){
lock.lock();
try {
while (flag)
cona.await();
System.out.println(Thread.currentThread().getName()+"--"+i + ":lidaofu");
flag = true;
conb.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void outB(int i){
lock.lock();
// 当只有线程调用这个方法时用if和while没有什么区别,当有多个线程调用这个方法时就要用while
try {
while (!flag)
// while是判断二次,if是只判断一次
conb.await();
System.out.println(Thread.currentThread().getName()+"--"+i + ":李道福");
flag = false;
cona.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadLockTest {
public static void main(String[] args) {
final Resource resource = new Resource();
//创建四个线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++)
resource.outA(i);
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++)
resource.outB(i);
}
}).start();
}
}
}
package li.dao.fu.test.thread;
/*
* 练习读写锁
* 读写锁与一般锁的区别,一般锁当锁定后其它线程就无法访问锁定的代码,读写锁是当锁定读时写线程无法进行操作而相同的
* 读线程可以继续进行
* */
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWriteTest{
private int data;
private ReadWriteLock lock=new ReentrantReadWriteLock();
private Lock rlock=lock.readLock();
private Lock wlock=lock.writeLock();
public void read(){
rlock.lock();
System.out.println("正在读数据");
System.out.println("data="+data);
rlock.unlock();
}
public void write(){
wlock.lock();
data=new Random().nextInt(100);
System.out.println("……正在写数据……");
wlock.unlock();
}
}
public class ThreadReadWriteLock {
public static void main(String[] args) {
final ReadWriteTest rwt=new ReadWriteTest();
//for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++)
rwt.read();
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++)
rwt.write();
}
}).start();
}
//}
}