Java--动态代理与线程池

枚举:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象

   枚举可以让编译器在编译时就可以控制源程序中填写的非法值
  
   枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法
   枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔
   把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误
  
   枚举只有一个成员时,就可以作为一种单例的实现方式

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 getConstructor(Class... parameterTypes) :返回某个公共构造函数
  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();
		}
	//}
}


你可能感兴趣的:(java)