Java剑开天门(四)

Java剑开天门(四),第1张

Java知识复习四

相关导航
Java剑开天门(一)
Java剑开天门(二)
Java剑开天门(三)
Java剑开天门(四)
Java剑开天门(五)


Java导航
  • Java知识复习四
  • 前言
  • 一、反射机制
    • 1、反射定义
    • 2、Class类简介
      • 2.1 反射实例化对象
        • 1)通过无参构造实例化对象
        • 2)通过有参构造实例化对象
    • 3、反射的应用
      • 3.1 调用getter和setter方法
      • 3.2 ClassLoader类
      • 3.3 代理机制
        • 1)静态代理
        • 2)动态代理
  • 二、注解
    • 1、注解的定义
    • 2、注解的应用
    • 3、元注解
      • 3.1 @Retention
      • 3.2 @Documented
      • 3.3 @Target
      • 3.4 @Inherited
      • 3.5 @Repeatable
    • 4、注解的属性
    • 5、Java预置的注解
      • 5.1 @Deprecated
      • 5.2 @Override
      • 5.3 @SuppressWarnings
      • 5.4 @SafeVarargs
      • 5.5 @FunctionalInterface
      • 5.6 文档注解
    • 6、注解与反射
    • 7、注解的应用场景
      • 7.1 提供信息给编译器
      • 7.2 编译阶段时的处理
      • 7.3 运行时的处理
  • 三、多线程
    • 1、线程和进程
    • 2、线程的生命周期
      • 2.1 新建 New
        • 1) Thread和Runnable的区别
      • 2.2 就绪 Runnable
      • 2.3 运行 Running
      • 2.4 阻塞 Blocked
      • 2.5 死亡 Dead
    • 3、线程调度
      • 3.1 调整线程优先级
      • 3.2 线程睡眠
      • 3.3 线程等待
      • 3.4 线程让步
      • 3.5 线程加入
      • 3.6 线程唤醒
    • 4、Thread类常用方法
      • 4.1 sleep(long millis)
      • 4.2 join()
      • 4.3 yield()
      • 4.4 setPriority()
      • 4.5 interrupt()
      • 4.6 wait()
      • 4.7 wait()和sleep()区别
        • 4.7.1 共同点
        • 4.7.2 不同点
      • 4.8 总结
    • 5、线程同步
      • 5.1 synchronized当作方法修饰符
      • 5.2 同步块
      • 5.3 将synchronized作用于static 方法
      • 5.4 总结
    • 6、线程数据传递
      • 6.1 通过构造方法传递数据
      • 6.2 通过变量和方法传递数据
      • 6.3 通过回调函数传递数据
    • 7、ReentrantLock类
      • 7.1 定义格式
      • 7.2 Lock中常用方法
      • 7.3 实现方式
      • 7.3 线程通信 Condition
      • 7.4 基于ReentrantLock的读写锁
    • 8、线程池
      • 8.1 线程池的使用
      • 8.3 线程池的工作原理
      • 8.3 线程池的参数详解
        • 8.3.1 任务队列(workQueue)
        • 8.3.2 线程工厂
        • 8.3.3 拒绝策略
      • 8.2 常见线程池
        • 8.1.1 newSingleThreadExecutor
        • 8.1.2 newFixedThreadExecutor(n)
        • 8.1.3 newCacheThreadExecutor(`推荐使用`)
        • 8.1.4 newScheduleThreadExecutor


前言

本博文重在夯实Java基础的知识点,回归书本,夯实基础,学深学精

近期需要巩固项目和找实习,计划打算将Java基础从头复习一遍,夯实基础,可能对Java的一些机制不了解或者一知半解,正好借此机会深入学习,也算记录自己的一个学习总结。
Java基础参考书籍:《Java 开发实战经典 名师讲坛 第2版》与《Java核心技术 卷I》


 本博文主要归纳整理Java应用程序设计方面的相关知识,如反射注解Annotation多线程相关知识点。

一、反射机制 1、反射定义

  什么是反射呢?
  反射,英文单词为·reflec·t,正常情况下我们是通过类去实例化对象,如果现有个需求——不知对象的类名称,就给一个对象的名称,如何去获取这个类的信息使用呢?这就是反射机制所做的事情。
  简单而言,就是通过对象去获取其类的一些信息和对其进行使用
  反射的包名称java.lang.reflect,是java.lang的子包。

2、Class类简介

  Java中也允许通过一个对象找到一个类的完整信息,即使用Class对应的功能。
  Class类是一个特殊类,它用于表示JVM运行接口的信息。
  Class类提供很多方法用于获取类的各种信息,比如获取类名、判断该类是否是一个接口还是`普通类等等。

  • Class类和Java类的关系

Object类是一切Java类的父类,对于普通的Java类,即便不声明,也是默认继承了Object类。典型的,可以使用Object类中的toString()方法。

Class类是一个特殊类,不继承Object类。用于Java反射机制的,一切Java,都有一个对应的Class对象,它是一个final类。Class 类的实例表示,正在运行的 Java 应用程序中的类和接口。

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。通过Object类getClass()的方法获取Class对象。

Object类的getClass方法
public final Class getClass();  //实际上Object是反射的源头:
所有类的对象实际上都是Class类的实例
获取Class对象的几种方法
方法一  类名.class
Class obj = String.class;
注意:使用这种办法生成Class类对象时,不会使JVM自动加载该类。
而其他办法会使得JVM初始化该类。

方法二 Class类的forName()方法
public class Person{};
Class obj = Class.forName("Person");
注意:此方法forName()括号内必须为类的--全路径名--.

方法三 使用对象的getClass()方法
public class Person {};
Person person = new Person();
//获得person的Class对象
Class obj = person.getClass(); 
//获得person父类的Class对象(继承情况)
Class obj1 = person.getSuperclass();
Class类的常用方法




public Object  newInstance()函数
创建  此 Class 对象  所表示的  类的  一个新实例。
此方法传入 null 构造方法所抛出的任何异常,包括已检查的异常。
使用此方法可以有效地绕过编译时的异常检查.
示例
public class Person{};
Class obj = Class.forName("Person");
Object newPerson = obj.newInstance();

注意:此处生成的是 Object类 的 实例 ,并非 Person类 的实例.


类型失真的解决方法
使用 转型 或者使用 泛化 的Class对象  生成带类型的实例.
public class Person{};
Class<Person> obj = Class.forName("Person");
Person newPerson = obj.newInstance();

缺点:使用泛化Class语法的对象 引用 不能 指向 别的类.
扩展:使用泛化Class语法构造此类对象的 父类对象 时要用 特殊方法

public class Person{};
class students extends Person{};
Class<students> obj = student.class;
Class<? super students> = student.getSuperClass();
————————————————————————————————————————————————————

public Field getField(String field_name)方法
返回一个 Field 对象,
它反映此 Class 对象所表示的类或接口的 全部  public成员字段 ,包括父类。

获得其他权限的成员字段方法
字段名.setAccessable()
当括号内参数为true,可以取消Java语言访问检查,即非Public权限字段也可访问.
————————————————————————————————————————————————————
public Field getDeclaredField(String field_name) 函数
和getField的区别在于它可以获得本类或本接口 任何权限 的 成员字段 .
————————————————————————————————————————————————————
public Field[] getFields()
得到本类的全部的public属性,其中包括父类的public属性。
public static void main(String[] args) throws IllegalAccessException {
        Student student = new Student();
        student.setGrade(99);
        student.setStudentNumber("20191223");
        student.setName("zhangsan");
        student.setAge(22);

        Field[] fields = student.getClass().getFields();
        for(Field field : fields){
            System.out.println("成员属性:"+field.getName()+" 成员属性修饰符: "+field.getModifiers()+" 成员属性值: "+field.get(student));
        }

        // 成员属性:studentNumber 成员属性修饰符: 1 成员属性值: 20191223
        // 成员属性:name 成员属性修饰符: 1 成员属性值: zhangsan
}
————————————————————————————————————————————————————
public Field[] getDeclaredFields()
获取某个类的自身的所有属性,不包括父类的属性。
注意:与getDeclaredField()方法一样,获取的是所有权限的属性。
privateprotectedpublic或者无修饰符都在内。

public static void main(String[] args) throws IllegalAccessException {
        Student student = new Student();
        student.setGrade(99);
        student.setStudentNumber("20191223");
        student.setName("zhangsan");
        student.setAge(22);

        Field[] fields = student.getClass().getDeclaredFields();
        for(Field field : fields){
            // 获取原来的访问控制权限
            boolean accessFlag = field.isAccessible();
            //if(!field.isAccessible()) field.setAccessible(true);
            System.out.println("成员属性:"+field.getName()+" 成员属性修饰符: "+field.getModifiers()+" 成员属性值: "+field.get(student));
            //field.setAccessible(accessFlag);
        }

        // 成员属性:studentNumber 成员属性修饰符: 1 成员属性值: 20191223
        // 成员属性:grade 成员属性修饰符: 2 成员属性值: 99.0
}

————————————————————————————————————————————————————
public Constructor getConstructor()方法
//public Constructor[] getConstructors() 函数
返回一个 Constructor 对象,
它反映此 Class 对象所表示的 类 的指定 公共构造方法 。
pulic class Person{};
Class<Person> obj = person.class;
Constructor<Person> con = obj.getConstructor();
————————————————————————————————————————————————————
public Method getMethod(String name,Class<?>... parameterTypes) 函数
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的
一个public成员方法,包括父类。
//getMethod第一个参数是方法名,第二个参数是该方法的参数类型
Method method = XXX.getClass().
getMethod(methodName,new Class[]{int.class,String.class});

获得其他权限的成员方法
字段名.setAccessable()//filed.setAccessable(true/false);
当括号内参数为true,可以取消Java语言访问检查,即非Public权限字段也可访问.
————————————————————————————————————————————————————
public Method getDeclareMethod(String name,Class<?>... parameterTypes)函数
返回一个 Method 对象,该对象反映此 Class 对象所表示本类或本接口一个public方法。
不包含父类的。
————————————————————————————————————————————————————
public Method[] getMethods()方法
返回一个 Method 数组,它反映此 Class 对象所表示的类或接口的
全部public成员方法,包括父类。
————————————————————————————————————————————————————
public Method[] getDeclareMethods()方法
返回一个 Method 数组,该对象反映此 Class 对象所表示本类或本接口全部public方法。
不包含父类的。
示例和getFields()方法类似,不重复

————————————————————————————————————————————————————

public Type getGenericSuperclass() 函数
返回表示此 Class 所表示的 实体(类、接口、基本类型或 void) 的直接超类的 Type。

父类泛型的类型探测

public student extends Person<student> {}
Sutdent student = new Student();
Class obj = student.class;
//获得父类type
Type type = obj.getGnericSuperclass();
// 参数化类型,即泛型
ParameterizeType p = (ParameterizeType) type;
//获得参数化类型数组
Type arr[] = p.getActualTypeArguments();
//获得父类泛型的类型
Class c = (Class)arr[0];
综上所述

Class<T> entityClass = (Class<T>)((ParameterizeType)getClass().getGnericSuperclass()).getActualTypeArguments()[0];
————————————————————————————————————————————————————
public Class[] getInterfaces()
返回一个Class类的对象数组,是表示一个类所实现的全部接口。
可以通过getName()直接获取接口的名称。
————————————————————————————————————————————————————
public String getName() 函数
以String形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
该方法的返回类型为String,它返回实体的名称。

```java
public class GetNameOfClass {

public static void main(String[] args) {

Thread th = new Thread();

//获取线程的Class对象

Class cl = th.getClass();

//它返回类的名称Thread-

String class_name = cl.getName();

//显示类别名称

System.out.println("Class Name :: " + class_name);

}

}

输出结果Class Name :: java.lang.Thread
————————————————————————————————————————————————————
2.1 反射实例化对象 1)通过无参构造实例化对象

  即使用newInstance()方法,但是一定要保证被实例化的类中存在一个无参构造方法

public class demo{	
	public static void main(String[] args) throws Exception {
		Class<?> c=null;
		c=Class.forNmae("org.yxy.demo.Person");//传入要实例化类的包.类名称
		Person per =null;
		per = (Person)c.newInstance(); 

	}	
}
2)通过有参构造实例化对象

  对于无无参构造的类来说,可以通过有参构造实例化对象。即明确的调用类中的构造方法。

  • 通过Class类中的getConstructors()取得本类中的全部构造方法;
  • 向构造方法中传递一个对象数组,里面包含了构造方法中所需的各个参数;
  • 通过Constructor实例化对象。

  Constructor类中的方法

public int getModifies()
得到构造方法的修饰符;
public String getName()
得到构造方法的名称;
public Class<?>[] getParameterTypes()
得到构造方法中的参数类型【所有】;
public String toString()
返回此构造方法的信息;
**重点**
public T newInstance(Object……initargs)throws……
向构造方法中传递参数,【实例化对象】;
public class demo{	
	public static void main(String[] args) throws Exception {
		Class<?> c=null;
		c=Class.forNmae("org.yxy.demo.Person");//传入要实例化类的包.类名称
		Person per =null;
		Constructor<?>[] cons=null;
		per = (Person)cons[0].newInstance("",""); 

	}	
}
3、反射的应用

  首先介绍表示类的结构的三个类

  • Constructor 表示类中的构造方法
  • Field 表示类中的属性
  • Method 表示类中的方法
    可以通过反射取得对象的类的所有构造方法、属性、方法、所实现的接口。
invoke方法来自Method类
格式
public Object invoke(Object obj,Object……args) throws ……
obj - 从中调用底层方法的对象(简单的说就是调用谁的方法用谁的对象)
args - 用于方法调用的参数 
返回值 - 返回方法所返回的值

如果方法正常完成,则将该方法返回的值返回给调用者;
如果该值为基本类型,则首先适当地将其包装在对象中。
但是,如果该值的类型为一组基本类型,则数组元素 不 被包装在对象中;
换句话说,将返回基本类型的数组。
如果底层方法返回类型为 void,则该调用返回 null
实例
package test922;
 
public class InvokeObj {
 
    public void show() {
        System.out.println("无参show()方法。");
    }
    public void show (String name) {
        System.out.println("show方法:" + name);
    }
    public String[] arrayShow (String[] arr) {
        return arr;
    }
    public String StringShow (String str) {
        return str;
    }
    public int intShow (int num) {
        return num;
    }
}
——————————————————————————————————————————————————————————
package test922;
 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class MethodInvokeTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<InvokeObj> clazz = InvokeObj.class;
        Method[] methods = clazz.getMethods();
        System.out.println("以下输出InvokeObj类的方法:");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("InvokeObj类的无参show()方法:");
        Method method1 = clazz.getMethod("show", null);
        //会执行无参show()方法
        Object obj = method1.invoke(new InvokeObj(),null);
        System.out.println("输出无参show()方法的返回值:"+obj);
        System.out.println("InvokeObj类的show()方法: ");
        Method method2 = clazz.getMethod("show", String.class);
        Object obj1 = method2.invoke(new InvokeObj(), "hello,world");
        System.out.println("输出有参show()方法: ");
        System.out.println("InvokeObj类的arrayShow()方法: ");
        Method method3 = clazz.getMethod("arrayShow", String[].class);
        String[] strs = new String[]{"hello", "world", "!"};
        //数组类型的参数必须包含在new Object[]{}中,否则会报IllegalArgumentException
        String[] strings = (String[]) method3.invoke(new InvokeObj(), new Object[]{strs});
        for (String str : strings) {
            System.out.println("arrayShow的数组元素:" + str);
        }
        System.out.println("InvokeObj类的StringShow()方法: ");
        Method method4 = clazz.getMethod("StringShow", String.class);
        String string = (String) method4.invoke(new InvokeObj(), "Thinking in java");
        System.out.println("StringShow()方法的返回值: " + string);
        System.out.println("InvokeObj类的intShow()方法: ");
        Method method5 = clazz.getMethod("intShow", int.class);
        int num = (int) method5.invoke(new InvokeObj(), 89);
        System.out.println("intShow()方法的返回值: " + num);
    }
}
——————————————————————————————————————————————————————————
以下输出InvokeObj类的方法:
public void test922.InvokeObj.show(java.lang.String)
public void test922.InvokeObj.show()
public java.lang.String[] test922.InvokeObj.arrayShow(java.lang.String[])
public java.lang.String test922.InvokeObj.StringShow(java.lang.String)
public int test922.InvokeObj.intShow(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
InvokeObj类的无参show()方法:
无参show()方法。
输出无参show()方法的返回值:null
InvokeObj类的show()方法: 
show方法:hello,world
输出有参show()方法: 
InvokeObj类的arrayShow()方法: 
arrayShow的数组元素:hello
arrayShow的数组元素:world
arrayShow的数组元素:!
InvokeObj类的StringShow()方法: 
StringShow()方法的返回值: Thinking in java
InvokeObj类的intShow()方法: 
intShow()方法的返回值: 89

3.1 调用getter和setter方法

  可以直接调用类中的getter和setter方法
  本质还是调用getMethod()方法进行实现

package zz.person;
 
class Person {
	private String name;
	private int age;
	public String getName(){
		return this.name;
	}
	public void setName(String name){
		this.name = name;
	}
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return this.age;
	}
	public String toString(){
		return "姓名:" + this.name + "\n年龄:" + this.age;
	}
}
------------------------------------
package zz.invokesetgetdemo;
import java.lang.reflect.Method;
 
public class InvokeSetGetDemo{
	public static void main(String []args){
		Class<?> c = null;
		Object obj = null;
		try{
			c = Class.forName("zz.Person");
		}catch (ClassNotFoundException e){
			e.printStackTrace();
		}
		try {
			obj = c.newInstance();
		}catch (InstantiationException e){
			e.printStackTrace();
		}catch (IllegalAccessException e){
			e.printStackTrace();
		}
			setter(obj, "name", "张泽", String.class);
			setter(obj, "age", 18, int.class);
			System.out.print("姓名:");
			getter(obj, "name");
			System.out.print("年龄:");
			getter(obj, "age");
	}
 
	/*
	 *@param obj  *** 作的对象
	 *@param att  *** 作的属性
	 *@param value 设置的值
	 *@param type 参数的类型
	 */
	public static void setter(Object obj, String att, Object value, Class<?>type){
		try {
			Method met = obj.getClass().
							getMethod("set" + initStr(att), type);//重点
			met.invoke(obj, value);
		}catch (Exception e){
			e.printStackTrace();
		}
	}
	public static void getter(Object obj, String att){
		try {
			Method met = obj.getClass().getMethod("get" + initStr(att));//重点
			System.out.println(met.invoke(obj));
		}catch (Exception e){
			e.printStackTrace();
		}
	}
	public static String initStr(String old){	// 将单词的首字母大写
		String str = old.substring(0,1).toUpperCase() + old.substring(1) ;
		return str ;
	}
}
3.2 ClassLoader类

  这个类的主要功能是可以由用户自己设置类的加载路径
  有三种类加载器

  • Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
  • Extension ClassLoader 用来进行扩展类的加载,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
  • AppCloader 加载classpath指定的类,是最常用的加载器。

  执行顺序

  • Bootstrap CLassloder 最先执行
  • Extention ClassLoader 其次执行
  • AppClassLoader 最后执行

  示例代码


public class ClassLoaderTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
	
		ClassLoader cl = Test.class.getClassLoader();
		
		System.out.println("ClassLoader is:"+cl.toString());
		
	}

}
输出
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
//可知,这个class文件是由AppClassLoader加载的



  • 每个类加载器都有一个父加载器
    使用getParent()方法获取
ClassLoader cl = Test.class.getClassLoader();
		
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
输出
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
-----------------------------------------------


System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
输出
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
java.lang.NullPointerException
	at ClassLoaderTest.main(ClassLoaderTest.java:13)

报错,这表明ExtClassLoader也没有父加载器
  • 父加载器不是父类
    getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况
    • 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
    • 由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。
      直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader
  • Bootstrap ClassLoader是由C++编写的。
    Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。
3.3 代理机制
  • 为某个对象提供一个代理【Proxy,主要是简介访问的作用】,以控制对这个对象的访问
  • 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。
  • 代理类负责请求的预处理过滤、将请求分派委托类处理、以及委托类执行完请求后的后续处理
  • 根据代理类的生成时间不同可以将代理分为 静态代理动态代理 两种。
1)静态代理
  • 定义
    由程序员创建或工具生成代理类的源码,编译代理类。所谓静态也就是在程序运行前代理类的字节码文件就已经存在代理类和委托类的关系在运行确定了。
  • 示例
/** 
 * 顶层接口 ​​​​​​​
 * 
 */  
public interface Person {  
    public void sayHello(String content, int age);  
    public void sayGoodBye(boolean seeAgin, double time);  
}
---------------------------  
/** 
 * 需要被代理的类 实现了一个接口Person ​​​​​​​
 * 
 */  
public class Student implements Person{  
  
    @Override  
    public void sayHello(String content, int age) {  
        // TODO Auto-generated method stub  
        System.out.println("student say hello" + content + " "+ age);  
    }  
  
    @Override  
    public void sayGoodBye(boolean seeAgin, double time) {  
        // TODO Auto-generated method stub  
        System.out.println("student sayGoodBye " + time + " "+ seeAgin);  
    }  
  
}  
---------------------------  
/** 
 * 静态代理,这个代理类也必须要实现和被代理类相同的Person接口 
 * @author yujie.wang 
 * 
 */  
public class ProxyTest implements Person{  
      
    private Person o;  
      
    public ProxyTest(Person o){  
        this.o = o;  
    }  
  //通过代理来间接访问
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        //s为被代理的对象,某些情况下 我们不希望修改已有的代码,我们采用代理来间接访问  
        Student s = new Student();  
        //创建代理类对象  
        ProxyTest proxy = new ProxyTest(s);  
        //调用代理类对象的方法  
        proxy.sayHello("welcome to java", 20);  
        System.out.println("******");  
        //调用代理类对象的方法  
        proxy.sayGoodBye(true, 100);  
  
    }  
  
    @Override  
    public void sayHello(String content, int age) {  
        // TODO Auto-generated method stub  
        System.out.println("ProxyTest sayHello begin");  
        //在代理类的方法中 间接访问被代理对象的方法  
        o.sayHello(content, age);  
        System.out.println("ProxyTest sayHello end");  
    }  
  
    @Override  
    public void sayGoodBye(boolean seeAgin, double time) {  
        // TODO Auto-generated method stub  
        System.out.println("ProxyTest sayHello begin");  
        //在代理类的方法中 间接访问被代理对象的方法  
        o.sayGoodBye(seeAgin, time);  
        System.out.println("ProxyTest sayHello end");  
    }  
  
}  
 
 
 
//输出
ProxyTest sayHello begin  
student say hellowelcome to java 20  
ProxyTest sayHello end  
******  
ProxyTest sayHello begin  
student sayGoodBye 100.0 true  
ProxyTest sayHello end  
2)动态代理

【Spring AOP的实现原理】

  • 定义
    动态代理是在程序运行时,运用反射机制动态创建而成。
  • 动态代理又分为两种实现方式
    JDK 动态代理
    CGlib 动态代理
  • JDK动态代理
    • JDK动态代理的代理类字节码(.class文件)在创建时,需要实现业务实现类所实现的接口作为参数。
  • CGlib 动态代理
    • 通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

  • 示例
涉及到java反射包下的一个接口InvocationHandler和一个类Proxy-----------------------------------------------
InvocationHandler{
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
}
此接口只定义了一个invoke()方法,有三个参数,意义如下
1)Object proxy  被代理的对象
2)Method method 需要调用的方法
3)Object[] args  方法调用所需要的参数
-----------------------------------------------
Proxy类是专门完成代理的 *** 作类,可以通过此类为一个或多个接口动态地实现类
提供了newProxyInstance()方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interface,InvocationHandler h)throws……
使用newProxyInstance可以动态生成类,有三个参数,意义如下
1)ClassLoader loader  类加载器
2)Class<?>[] interfaces 得到的全部接口
3)InvocationHandler h  得到InvocationHandler的接口的子类实例
-----------------------------------------------
/**
 * LogProxy
 *
 * @author venlenter
 * @Description: TODO
 * @since unknown, 2021-12-30
 */
public class LogProxy {
    public static void main(String[] args) {
        Person person = (Person) getObject(new Student());
        person.sayHello("小明", 20);
        person.sayGoodBye(true, 30);
    }
    /**
     * 生成对象的代理对象,对被代理对象进行所有方法日志增强
     * 参数:原始对象
     * 返回值:被代理的对象
     * JDK 动态代理
     * 基于接口的动态代理
     * 被代理类必须实现接口
     * JDK提供的
     */
    public static Object getObject(final Object obj) {
        /**
         * 创建对象的代理对象
         * 参数一:类加载器
         * 参数二:对象的接口
         * 参数三:调用处理器,代理对象中的方法被调用,都会在执行方法。对所有被代理对象的方法进行拦截
         */
        Object proxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader()
                , obj.getClass().getInterfaces(), new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //方法执行前
                        long startTime = System.currentTimeMillis();
                        System.out.println(String.format("%s方法执行-开始时间:%s", method.getName(), startTime));
 
                        Object result = method.invoke(obj, args);//执行方法的调用
 
                        //方法执行后
                        long endTime = System.currentTimeMillis();
                        System.out.println(String.format("%s方法执行-结束时间:%s;方法执行耗时:%s", method.getName(), endTime, endTime - startTime));
                        return result;
                    }
                });
        return proxyInstance;
    }
 
//    /**
//     * 使用CGLib创建动态代理对象
//     * 第三方提供的的创建代理对象的方式CGLib
//     * 被代理对象不能用final修饰
//     * 使用的是Enhancer类创建代理对象
//     */
//    public static Object getObjectByCGLib(final Object obj) {
//        /**
//         * 使用CGLib的Enhancer创建代理对象
//         * 参数一:对象的字节码文件
//         * 参数二:方法的拦截器
//         */
//        Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {
//            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//                //方法执行前
//                long startTime = System.currentTimeMillis();
//
//                Object invokeObject = method.invoke(obj, objects);//执行方法的调用
//
//                //方法执行后
//                long endTime = System.currentTimeMillis();
//                SimpleDateFormat sdf = new SimpleDateFormat();
//                System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n"
//                        , method.getName()), sdf.format(endTime), endTime - startTime);
//                return invokeObject;
//            }
//        });
//        return proxyObj;
//    }
}
 
 
//输出
sayHello方法执行-开始时间:1641181201374
student say hello小明 20
sayHello方法执行-结束时间:1641181201428;方法执行耗时:54
sayGoodBye方法执行-开始时间:1641181201429
student sayGoodBye 30.0 true
sayGoodBye方法执行-结束时间:1641181201430;方法执行耗时:1

  • 总结
    JDK的代理让我们在不直接访问某些对象的情况下,通过代理机制也可以访问被代理对象的方法
    这种技术可以应用在很多地方比如RPC框架Spring AOP机制,但是我们看到JDK的代理机制必须要求被代理类实现某个方法,这样在生成代理类的时候才能知道重新那些方法。
    这样一个没有实现任何接口的类就无法通过jJDK的代理机制进行代理,当然解决方法是使用CGlib的代理机制进行代理。
二、注解

  注解(Annotation)为Java代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

想像代码具有生命,注解就是对于代码中某些鲜活个体的贴上去的一张标签。
简化来讲,注解如同一张标签

  其实同 classs 和 interface 一样,注解也属于一种类型

1、注解的定义
  • 定义格式
public @interface TestAnnotation {

}
它的形式跟接口很类似,不过前面多了一个 @ 符号。
2、注解的应用
  • 使用方法
@TestAnnotation
public class Test {

}
在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了。
3、元注解
  • 定义
    元注解是可以注解到注解上注解,或者说元注解是一种基本注解,但是它能够应用其它注解上面。

元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的

  • 元注解的种类
    元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

3.1 @Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间
注解的取值有三种
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {


}


3.2 @Documented

这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。


3.3 @Target

Target 是目标的意思,@Target 指定了注解运用的地方。【地点】
当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。

  @Target的取值
    ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
    ElementType.CONSTRUCTOR 可以给构造方法进行注解
    ElementType.FIELD 可以给属性进行注解
    ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    ElementType.METHOD 可以给方法进行注解
    ElementType.PACKAGE 可以给一个包进行注解
    ElementType.PARAMETER 可以给一个方法内的参数进行注解
    ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举


3.4 @Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,子类继承超类的注解。
而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承超类的注解

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test@Inherited 修饰,之后类 ATest 注解,类 B 继承 A,B 也拥有 Test 这个注解。

老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。


3.5 @Repeatable

Repeatable 自然是可重复的意思。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

@interface Persons {
    Person[]  value();
}
@Repeatable(Persons.class)
@interface Person{
    String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码,
@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。
  • 什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。
@interface Persons {
    Person[]  value();
}
它里面必须要有一个 value 的属性,
属性类型是一个被 @Repeatable 注解过的注解数组,
注意它是**数组**

Persons 是一张总的标签,上面贴满了 Person 这种同类型内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
首先我们要定义一个容器注解总注解】,其次把这个总注解通过@Repeatable(总注解.class)这样的形式给总注解里的数组类型的注解进行注解,即可重复叠加相同的注解不同的值

4、注解的属性
  • 注解的属性也叫做成员变量。注解只有成员变量没有方法
  • 注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量名字,其返回值定义了该成员变量的类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String msg();
}

  • 在使用的时候,我们应该给它们进行赋值。
    赋值的方式是在注解的括号内 value= 形式,多个属性之前用 ,隔开`。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}

  • 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 接口注解及它们的数组
  • 注解中属性可以有默认值,默认值需要用 default 关键字指定。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default -1;
    public String msg() default "Hi";
}

  • 还需要注意的一种情况是一个注解没有任何属性
public @interface Perform {}

@Perform
public void testMethod(){}
那么在应用这个注解的时候,括号都可以省略。
5、Java预置的注解 5.1 @Deprecated

  这个元素是用来标记过时的元素。
  编译器在编译阶段遇到这个注解时会发出提醒警告告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

public class Hero {
    @Deprecated
    public void say(){
        System.out.println("Noting has to say!");
    }
    public void speak(){
        System.out.println("I have a dream!");
    }
}

5.2 @Override

提示子类重写父类被 @Override 修饰的方法

5.3 @SuppressWarnings

阻止警告的意思。
之前说过调用被@Deprecated注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过@SuppressWarnings达到目的

@SuppressWarnings("deprecation")
public void test1(){
    Hero hero = new Hero();
    hero.say();
    hero.speak();
}

5.4 @SafeVarargs

参数安全类型注解。
它的目的是提醒开发者不要用参数做一些不安全的 *** 作,它的存在会阻止编译器产生 unchecked 这样的警告

@SafeVarargs // Not actually safe!
    static void m(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList; // Semantically invalid, but compiles without warnings
    String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
上面的代码中,编译阶段不会报错,
但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。

5.5 @FunctionalInterface

函数式接口注解。
函数式接口 (Functional Interface) 就是一个只具有一个方法的普通接口。

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface Runnable is used
     * to create a thread, starting the thread causes the object's
     * run method to be called in that separately executing
     * thread.
     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

函数式接口可以很容易转换为 Lambda 表达式

5.6 文档注解
Tag标记意义格式
@author确定类的作者@author description
@deprecated指示反对使用这个类或成员@deprecated description
{@docRoot}指定当前文档的根目录路径 (Java 2的1.3版新增)
@exception确定一个方法引发的异常@exception exception-name explanation
{@link}插入对另一个主题的内部链接{@link name text}
@param为方法的参数提供文档@param parameter-name explanation
@return为方法的返回值提供文档@return explanation
@see指定对另一个主题的链接@see anchor @see pkg.class#member text
@serial为默认的可序列化字段提供文档@serial description
@serialData为writeObject( )或者writeExternal( )方法编写的数据提供文档@serialData description
@serialField为ObjectStreamField组件提供文档@serialField name type description
@since当引入一个特定改变时,声明发布版本@since release
@throws与@exception相同@throws 标记与@exception标记的含义相同。
@version指定类的版本@version info
示例
/**

 * @author: zhangsan

 * @createDate: 2020/12/28

 * @description: this is the student class.

 */
/**

 * @param num1: 加数1

 * @param num2: 加数2

 * @return: 两个加数的和

 */
   /**

    * @description: 构造方法

    * @param name: 学生姓名

    * @param age: 学生年龄

    */
  • IDEA编译器快速生成类注释
    • 按照顺序打开File–>settings–>Editor–>File and Code Templates–>Includes–>File Header
    • 按照顺序打开File–>settings–>Editor–>File and Code Templates–>Files–>Class
    • 输入类注释代码(按照自己需求来即可)
/**
 * @author ${USER}
 * @Description ${NAME}
 * @Date: ${DATE} ${HOUR}:${MINUTE}
 */

  • IDEA编译器快速生成方法注释
    • 依次打开File–>Settings–>Editor–>Live Templates
    • 点击右边的”+“号,选择Template Group,创建自定义的新组
    • 选择自己的组名,点击”+”号,选择Live Template,创建新的活动模板,其中Abbreviation输入自定义的快捷字,类似于psvm、sout,使用时输入加上Tab键即可
    • Template text中需要填写模板代码
*
 * @Description $description$
 * @param $params$
 * @return $returns$
 * @author feizhen
 * @Date $date$
 */

    • 点击Template text下方的Define,按需求选择,一般选择java即可
    • 只有第一次会出现Define,如果是想修改,可以右键点击模板名,选择Change context,编辑变量,获取参数
    • 我们可以使用脚本,不再使用idea自带的方法
groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\[|\\]|\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {if(params[i] == '') continue;if(i==0) result += params[0]+((params.size()==1)?'':'\n');else{ result+=' * @param ' + params[i] + ((i < params.size() - 1) ? '\n' : '')}}; return result", methodParameters())

6、注解与反射

通过用标签来比作注解,前面的内容是讲怎么写注解,然后贴到哪个地方去,而现在我们要做的工作就是检阅这些标签内容。
形象的比喻就是你把这些注解标签在合适的时候撕下来,然后检阅上面的内容信息。
要想正确检阅注解,离不开一个手段,那就是反射

注解通过反射获取。

  • 可以通过 Class 对象的 isAnnotationPresent() 方法判断【Class对象】是否应用了某个注解
public boolean isAnnotationPresent(
Class<? extends Annotation> annotationClass) {}

  • 通过 getAnnotation() 方法来获取 Annotation 对象
 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

  • 或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}

  • 示例
@TestAnnotation()
public class Test {
    public static void main(String[] args) {
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }
    }
}

7、注解的应用场景

值得注意的是,注解不是代码本身的一部分。

7.1 提供信息给编译器

编译器可以利用注解来探测错误和警告信息。

7.2 编译阶段时的处理

软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理

7.3 运行时的处理

某些注解可以在程序运行的时候接受代码的提取


注解主要针对的是编译器和其它工具软件(SoftWare tool)。

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码提取并处理 Annotation 信息。这些处理提取和处理 Annotation 代码统称为 APT(Annotation Processing Tool)

三、多线程 1、线程和进程
  • 进程是系统进行资源调度和分配的基本单元,是 *** 作系统的基础
    每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销
  • 线程系统调度的最小单元,是进程的运算单元一个进程可能包含一个或者多个线程
    同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小
2、线程的生命周期

线程的生命周期有5个
新建、就绪、运行、阻塞、销毁

2.1 新建 New

线程的创建有3种方式

  • Thread类创建
  • 实现Runnable接口
  • Callable接口&Future创建

示例

 # 1、thread
new Thread() {
	@Override
	 public void run() {
	 }
}.start();
----------------------------
# runnable
public class RunnableThread implements Runnable {
    @Override
    public void run() {
        System.out.println("runnable thread");
    }
    public static void main(String[] args){
        Thread t = new Thread(new RunnableThread());
        t.start();
    }
}
----------------------------
# Callable&Future 【不常用】
public class CallableThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable Thread return value");
        return 0;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> future = new FutureTask<Integer>(new CallableThread());
        new Thread(future).start();
        System.out.println(future.get());
    }
}
其实,比较细心看的话,最终都是Runnable一种方式实现,都需要实例化 Thread类 。
1) Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
实现Runnable接口比继承Thread类所具有的优势

  • 适合多个相同的程序代码的线程去处理同一个资源

  • 可以避免Java中的单继承的限制

  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

  • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM就是在 *** 作系统中启动的一个进程。

start()和run()方法

  • 线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。
  • 另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法

总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码

2.2 就绪 Runnable

线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权

public class RunStartThread extends Thread {
    public RunStartThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis());
        try {
            sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        RunStartThread rst = new RunStartThread("runThread");
        rst.run(); // 主线程运行run方法
        rst.start(); // 启动子线程运行run
    }
}
当运行start()方法时,发现目前正在运行的线程名称是我自定义的线程名称runThread.
2.3 运行 Running

就绪状态的线程获取了CPU的使用权,执行程序代码。

2.4 阻塞 Blocked

阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
阻塞的情况分为三种

  • 等待阻塞
    运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
  • 同步阻塞
    运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞
    运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep()是不会释放持有的锁

线程进入Blocked状态时一般是自动等待后进入运行状态或者直接死亡结束,一般导致Blocked的是synchronized关键字
我们开发时主要尽量不要使用synchronized(自适应内旋锁,CAS当重试无多次无法获取锁时,进入重量锁,出现Blocked现象),原因是锁匙自动释放不可控,此外是单线程运行同一对象不能同时运行,如果真需要控制线程安全性的编程,尽量用Lock类及其子类ReentrantLock

public class BlockThreads {

   public static void main(String[] args) throws InterruptedException {
       TestThread th = new TestThread();
       th.runThread(th,"Thread1");
       th.runThread(th,"Thread2");
       th.runThread(th,"Thread3");
       System.out.println("111");
   }

   private static class TestThread {
       public synchronized void sayHello() throws InterruptedException {
           System.out.println(System.currentTimeMillis());
           Thread.sleep(3000);
       }

       public void runThread(TestThread th, String threadName) {
           new Thread(threadName) {
               @Override
               public void run() {
                   try {
                       th.sayHello();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }.start();
       }
   }
}

  • Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程共享资源进行访问的工具。提供了对共享资源的独占访问,每次只能有一个线程Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
class A{
    private final ReentrantLock lock = new ReentrantLock();
    public void run(){
        lock.lock();
        try{
            //保证线程安全的代码
        }finally{
            lock.unlock();
            //如果同步代码有异常,需将unlock()写入finally语句块
        }
    }
}
-------------------------------
package Thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DemoLock {
    public static void main(String[] args) {
        Lock1 lock1 = new Lock1();
        new Thread(lock1).start();
        new Thread(lock1).start();
        new Thread(lock1).start();
    }
}

class Lock1 implements Runnable{
    int tickets = 10;
//定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){

            try {
                //加锁
                lock.lock();
                if (tickets>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(tickets--);
                }else {
                    break;

                }
            }finally {
                //解锁
                lock.unlock();
            }

        }
    }
}

  • Lock是显式锁手动开启和关闭锁,别忘记关闭锁synchronized隐式锁处理作用域自动释放
  • Lock只有代码块锁synchronized有代码块和方法锁
  • 使用Lock锁,JVM将花费较少的时间调度线程性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序
    Lock>同步代码块(已经进入了方法体,分配了相应资源>同步方法(在方法体之外)
2.5 死亡 Dead

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3、线程调度 3.1 调整线程优先级

Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量。

static int MAX_PRIORITY
          线程可以具有的最高优先级,取值为10static int MIN_PRIORITY
          线程可以具有的最低优先级,取值为1static int NORM_PRIORITY
          分配给线程的默认优先级,取值为5。
          
主线程的默认优先级为Thread.NORM_PRIORITY。

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的 *** 作系统都不能很好的映射。
如果希望程序能移植到各个 *** 作系统中,
应该仅仅使用Thread类有以下三个静态常量作为优先级,
这样能保证同样的优先级采用了同样的调度方式。
3.2 线程睡眠

Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

3.3 线程等待

Object类中的wait()方法,导致当前的线程等待,直到其他线程调用对象notify() 方法notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

3.4 线程让步

Thread.yield() 方法暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

3.5 线程加入

join()方法等待其他线程终止。在当前线程中调用另一个线程join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态

3.6 线程唤醒

Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
线程通过调用其中一个 wait 方法在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程

4、Thread类常用方法 4.1 sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

4.2 join()

指等待该线程终止。
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法,后面的代码,只有等到子线程结束了才能执行。

Thread t = new AThread();
 t.start(); 
 t.join();

在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

public class Main {
 
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
		Thread1 mTh1=new Thread1("A");
		Thread1 mTh2=new Thread1("B");
		mTh1.start();
		mTh2.start();
		try {
			mTh1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try {
			mTh2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
 
	}
 
}
4.3 yield()

Thread.yield()方法作用是
暂停当前正在执行的线程对象,并执行其他线程
yield()应该做的是让当前运行线程回到就绪状态,以允许具有相同优先级的其他线程获得运行机会


package com.multithread.yield;
class ThreadYield extends Thread{
    public ThreadYield(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
            if (i ==30) {
                this.yield();
            }
        }
	
}
}
 
public class Main {
 
	public static void main(String[] args) {
		
		ThreadYield yt1 = new ThreadYield("张三");
    	ThreadYield yt2 = new ThreadYield("李四");
        yt1.start();
        yt2.start();
	}
 
}
4.4 setPriority()

更改线程的优先级。

Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
4.5 interrupt()

不要以为它是中断某个线程!
它只是向线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的

4.6 wait()

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行 *** 作。
语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内
功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
相应的notify()就是对对象锁的唤醒 *** 作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束自动释放锁后,JVM会在wait()对象锁的线程随机选取一线程赋予其对象锁唤醒线程,继续执行。
这样就提供了在线程间同步、唤醒的 *** 作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制


建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:


package com.multithread.wait;
public class MyThreadPrinter2 implements Runnable {   
	  
    private String name;   
    private Object prev;   
    private Object self;   
  
    private MyThreadPrinter2(String name, Object prev, Object self) {   
        this.name = name;   
        this.prev = prev;   
        this.self = self;   
    }   
  
    @Override  
    public void run() {   
        int count = 10;   
        while (count > 0) {   
            synchronized (prev) {   
                synchronized (self) {   
                    System.out.print(name);   
                    count--;  
                    
                    self.notify();   
                }   
                try {   
                    prev.wait();   
                } catch (InterruptedException e) {   
                    e.printStackTrace();   
                }   
            }   
  
        }   
    }   
  
    public static void main(String[] args) throws Exception {   
        Object a = new Object();   
        Object b = new Object();   
        Object c = new Object();   
        MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   
        MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   
        MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);   
           
           
        new Thread(pa).start();
        Thread.sleep(100);  //确保按顺序A、B、C执行
        new Thread(pb).start();
        Thread.sleep(100);  
        new Thread(pc).start();   
        Thread.sleep(100);  
        }   
}  
 

主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁
主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。

4.7 wait()和sleep()区别 4.7.1 共同点
  • 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  • wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException
    • 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException在catch() {} 中直接return即可安全地结束线程
    • 需要注意的是,InterruptedException线程自己从内部抛出的,并不是interrupt()方法抛出的。
    • 对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException
4.7.2 不同点
  • 所属类不同
    Thread类的方法:sleep(),yield()等
    Object类的方法:wait()和notify()等
  • 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
    最大的区别就是
  • sleep()睡眠时,保持对象锁,仍然占有该锁;
  • 而wait()睡眠时,释放对象锁。
4.8 总结
	sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
    currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。
  (用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 
  setPriority(): 设置一个线程的优先级。
5、线程同步

Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。

  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

  • 每个对象只有一个锁(lock)与之相关联。

  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制

synchronized用到不同地方对代码产生的影响
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
拿到锁就是这个synchronized关键字在哪地方出现哪地方拿到了这个对象锁

5.1 synchronized当作方法修饰符
public synchronized void methodAAA(){
		//….
}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法的对象。
也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

等价于如下代码
public void methodAAA(){
		synchronized (this){      //  (1)
		       //…..
		}
}

(1)处的this指的是什么呢?
它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。

5.2 同步块
public void method3(SomeObject so){
         synchronized(so){
		       //…..
		}
}

锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)充当锁

class Foo implements Runnable{
        private byte[] lock = new byte[0];  // 特殊的instance变量
	    public void methodA(){
	       synchronized(lock) {
	        	//… 
	       }
	}
//…..
}
5.3 将synchronized作用于static 方法
class Foo{
	public synchronized static void methodAAA(){   // 同步的static 函数
			 //….
	}
	public void methodBBB(){
	      synchronized(Foo.class)   //  class literal(类名称字面常量)
	}
 }

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法对象所属(Class,而不再是由这个Class产生的某个具体对象了)。
如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样A方法的锁Obj这个对象,而B的锁Obj所属的那个Class

5.4 总结
  • 线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏
  • 线程同步方法是通过来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程无法再访问该对象的其他非同步方法
  • 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
  • 对于同步,要时刻清醒在哪个对象上同步,这是关键。
  • 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子” *** 作做出分析,并保证原子 *** 作期间别的线程无法访问竞争资源。
  • 当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
  • 死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。但是,一旦程序发生死锁,程序将死掉。
6、线程数据传递

在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。
由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。

6.1 通过构造方法传递数据

在创建线程时,必须要建立一个Thread类的或其子类的实例
在调用start()方法之前通过线程类的构造方法将数据传入线程。
并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。

 
package mythread; 
public class MyThread1 extends Thread { 
	private String name; 
	public MyThread1(String name) { 
		this.name = name; 
	} 
	public void run() { 
		System.out.println("hello " + name); 
	} 
	public static void main(String[] args) { 
		Thread thread = new MyThread1("world"); 
		thread.start(); 
	} 
} 

如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便

6.2 通过变量和方法传递数据

向对象中传入数据一般有两次机会
第一次机会是在建立对象时通过构造方法将数据传入;
另外一次机会就是在中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值

 
package mythread; 
public class MyThread2 implements Runnable { 
	private String name; 
	public void setName(String name) { 
		this.name = name; 
	} 
	public void run() { 
		System.out.println("hello " + name); 
	} 
	public static void main(String[] args) { 
		MyThread2 myThread = new MyThread2(); 
		myThread.setName("world"); 
		Thread thread = new Thread(myThread); 
		thread.start(); 
	} 
} 
6.3 通过回调函数传递数据

以上两种方法都是main方法中主动将数据传入线程类的。
在有些应用中需要在线程运行的过程中动态地获取数据
如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。
从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value无法事先就传入线程类的。
事先并未设置value的初始值,通过回调函数设置初始值

 
package mythread; 
class Data { 
	public int value = 0; 
} 
class Work { 
	public void process(Data data, Integer numbers) { 
		for (int n : numbers) { 
			data.value += n; 
		} 
	} 
} 

public class MyThread3 extends Thread { 
	private Work work; 
	public MyThread3(Work work) { 
		this.work = work; 
	} 
	public void run() { 
		java.util.Random random = new java.util.Random(); 
		Data data = new Data(); 
		int n1 = random.nextInt(1000); 
		int n2 = random.nextInt(2000); 
		int n3 = random.nextInt(3000); 
		work.process(data, n1, n2, n3); // 使用回调函数 
		System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" 
		+ String.valueOf(n3) + "=" + data.value); 
	} 
	public static void main(String[] args) { 
		Thread thread = new MyThread3(new Work()); 
		thread.start(); 
	} 
} 
7、ReentrantLock类

在Java SE 5.0之后,Java引入了一个ReentrantLock类,也可以实现给代码块上锁释放锁的效果。原理剖析
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
Reentrant 锁意味着什么呢?
简单来说,它有一个与相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。

  • 这模仿了 synchronized 的语义
    如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

lock接口也是对资源上锁的一种工具,它不同于Synchronized的隐式上锁与解锁,lock接口中的获取锁和释放锁,是需要手动去 *** 作的。

Synchronized的缺陷

  • 多个线程去获取锁资源,只有一个线程可以获得锁,其他线程只能阻塞,什么事情都做不了。因此,需要一种机制让处于阻塞的线程可以中断锁的获取,也就是抛弃某些线程。(解决方案:Lock接口中的lockInterruptibly(),或者trylock(long time,TimeUnit unit))。
  • 当文件被读写时,读 *** 作和写 *** 作同样要获取当前文件的锁资源,Synchronized有且仅有一个线程访问当前文件并进行读或者写。此时效率会低下。因此,需要一种机制来使得当多个线程都只是进行读 *** 作时,线程之间不会发生冲突。(解决方案:ReadWriteLock接口实现类ReentrantReadWtriteLock)。
  • 我们无法得知当前线程是否获得了锁。因此我们需要一个方法来查看当前锁是否获取到了。(解决方案 : Lock接口中的tryLock())。

尽量使用此Lock或其子类ReentrantLock进行线程同步。

7.1 定义格式
Lock lock = new ReentrantLock();
lock.lock();//加锁
try{
    //共享代码
}catch{

}finally{
    lock.unlock(); //释放锁
}

7.2 Lock中常用方法
lock(); //获取锁

unlock(); //释放锁

trylock(); //尝试获取锁,获取到了返回true,获取不到返回false; 可轮询

trylock(long time ,TimeUnit unit); // 在一定时间内获取锁,获取到了返回true,获取不到返回false;可定时的

lockInterruptibly(); //获取锁,但是可以被中断 可响应中断
    //具体意思就是这个方法和lock是一样的,
    //都是获取锁的方法,但是它可以被interrupt中断,中断了获取锁的状态,
    //就好比直接将当前线程抛弃。

Condition  newCondition(); //生成一个Condition,可以对锁对象的进行等待,唤醒等 *** 作,也就是用于线程通信

demo
public class TestDemo {
    private static final Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+" got the lock");
        }finally {
            System.out.println(Thread.currentThread().getName()+" release the lock");
            lock.unlock();
        }
    }
}

ReentrantLock是一个可重入锁
什么叫做可重入锁呢,就是当前线程中的方法对锁资源进行获取之后,方法内部中还有一个线程进行获取,此时就不需要再获取锁了,直接进入。这就是可重入锁
Synchronized也是可重入锁,在讲解monitor中就可以看出。

7.3 实现方式

ReentrantLock主要利用CAS+CLH队列来实现。它支持公平锁非公平锁,两者的实现类似。

  • 非公平锁:随机的获取,谁运气好,谁先获得CPU使用权,哪个线程就先获取执行。
  • 公平锁:第一次获取锁的线程,在下一次执行也会优先获取
public ReentrantLock()//无参构造函数 -> 非公平锁
public ReentrantLock(boolean fair)//true表示公平锁,false表示非公平锁
public class TestDemo {
    private static final Lock lock = new ReentrantLock(true); //公平锁
    public static void test(){
        for(int i=0; i<3; i++){
           lock.lock();
           try {
               System.out.println(Thread.currentThread().getName()+" got the lock");
               TimeUnit.MILLISECONDS.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           } finally {
               lock.unlock();
           }
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<5; i++){
            new Thread("线程"+i){
                @Override
                public void run() {
                    test();
                }
            }.start();
        }
    }
}

7.3 线程通信 Condition

Condition也可以与Object一样,将线程置为Waiting状态,让对应的方法去唤醒。

Condition接口中的方法

  • Conidtion的await()对应Objcet的`wait();将满足当前Condition条件的线程加入到等待队列,释放其中的锁。
  • Condition的signal()对应Object的notify();唤醒一个在等待队列中的线程。
  • Condition的signalAll()对应Object的notifyAll();唤醒所有在等待队列中的线程。

Condition是ReentrantLock可重入锁类下实现的,一个ReentrantLock可以对一个或多个Condition

Condition con = lock.newCondition();
con.await()

注意事项

  • 使用await()必须要使用lock.lock()加锁 反之抛出IllegalMonitorStateException。
  • 使用await()使得当前线程阻塞,await()之后的代码不会去执行。
  • await()方法调用之后会释放当前的lock。
  • signal()方法唤醒一个线程,但是该方法执行之后是不会立即释放锁,出了锁的作用域才会将锁释放掉。
  • await方法是可中断方法,可中断方法会收到中断异常InterruptException异常。

示例

有三个线程A,B,C,需要线程交替打印ABCABCABCABC…, 打印10次。
必须借助await/signal/signalAll去实现。

0   1   2   3   4   5   6   7   8   9
A   B   C   A   B   C   A   B   C   A

A:count % 3 == 0
B:count % 3 == 1
C:count % 3 == 2


---------------
public class TestDemo13 {
    private static final Lock lock = new ReentrantLock();
    private static final Condition A = lock.newCondition(); //count % 3 != 0
    private static final Condition B = lock.newCondition(); //count % 3 != 1
    private static final Condition C = lock.newCondition();
    private static int count = 0;

    public static void main(String[] args) {
        Thread threadA = new Thread("线程1") {
            @Override
            public void run() {
                lock.lock();
                try {
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 0) {
                            //阻塞线程1 阻塞在A这个条件上
                            A.await();
                        }
                        System.out.print("A");
                        count++;
                        B.signalAll(); //唤醒阻塞在B条件上的所有线程
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread threadB = new Thread("线程2") {
            @Override
            public void run() {
                lock.lock();
                try {
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 1) {
                            B.await(); //阻塞线程2 阻塞在B这个条件上
                        }
                        System.out.print("B");
                        count++;
                        C.signalAll(); //线程2执行完之后唤醒阻塞在C条件上线程
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread threadC = new Thread("线程3") {
            @Override
            public void run() {
                lock.lock();
                try {
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 2) {
                            C.await(); //阻塞线程3 阻塞在C这个条件上
                        }
                        System.out.print("C");
                        count++;
                        A.signalAll(); //线程3执行完之后唤醒阻塞在A条件上线程
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

我们可以通过一个lock对象创建多个Condition条件,即对应多个阻塞队列,因此我们可以通过多个Condition对象调用await()/signal()/signalAll()方法,相当于一个房子多个门。
相比于前面syncrhoized加锁时的一个阻塞队列(一个房子一个门)而言,我们可以在不同的情况下访问同一个Condition,相比于以前使用更加方便,更好的解决wait()/notify()/notifyAll()所带来的问题。

7.4 基于ReentrantLock的读写锁
ReadWriteLock <-> ReentrantReadWriteLock

共享读,只能一个写。

总结:读读不互斥、读写互斥、写写互斥。

读远远大于写高并发情况下,一般会使用读写锁

class ReadWriteLockDemo {
    private int num = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void setNum(int num) {
        this.num = num;
    }

    public void read() {
        lock.readLock().lock();
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "::正在进行读 *** 作," + num);
            }
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write(Integer newNum) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "::正在进行写 *** 作," + newNum);
            setNum(newNum);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();
        for (int i = 0; i < 5; i++) {
            new Thread("wrire " + i) {
                @Override
                public void run() {
                    demo.write(new Random().nextInt(1001));
                }
            }.start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread("read " + i) {
                @Override
                public void run() {
                    demo.read();
                }
            }.start();
        }
    }
}

深入理解读写锁——ReadWriteLock源码分析

8、线程池

java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务时间,T3 销毁线程时间
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能

总体来说,线程池有如下的优势

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池技术正是关注如何缩短或调整T1、T3时间的技术,从而提高服务器程序性能的。它把T1、T3分别安排服务器程序的启动结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1、T3的开销了。

示例 显著减少了创建线程的数目
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

8.1 线程池的使用

线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
-------------------------------------------- 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
-------------------------------------------- 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
-------------------------------------------- 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • 其需要如下几个参数
  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:- TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池的使用流程如下

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
8.3 线程池的工作原理

见图说话

8.3 线程池的参数详解 8.3.1 任务队列(workQueue)

任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  • LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  • DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  • SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
  • LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
  • LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
8.3.2 线程工厂

线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂

/**
 * The default thread factory.
 */
private static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}
8.3.3 拒绝策略

当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。
不过 Executors 框架已经为我们实现了 4 种拒绝策略

  • AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy:由调用线程处理该任务。
  • DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
  • DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
8.2 常见线程池

Executors已经为我们封装好了 4 种常见的功能线程池

8.1.1 newSingleThreadExecutor

单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务

8.1.2 newFixedThreadExecutor(n)

固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行

8.1.3 newCacheThreadExecutor(推荐使用

可缓存线程池, 当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

8.1.4 newScheduleThreadExecutor

大小无限制的线程池,支持定时和周期性的执行线程

Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/877027.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存