Java进阶知识包括:注解、反射、多线程。
注解
1.1 什么是注解
- 注解和反射是所有框架的底层
- 从JDK5.0开始引入
作用
- 不是程序本身,可以对程序作出解释
- 可以被其他程序(比如:编译器等)读取
- 注解有检查和约束的功能
格式
- 注解是以“@注释名“在代码中存在的,还可以添加一些参数
- @SuppressWarnings(value=“unchecked”)
使用位置
- 可以在package,class,method,field上面,相当于给他们添加了额外的辅助信息
- 可以通过反射机制编程,实现对这些元数据的访问,
1.2 内置注解
- @Override:定义在java.lang.Override中,此注解只适用于方法,表示一个方法声明打算重写父类中的另一个方法声明。
- @Deprecated:定义在java.lang.Deprecated中,此注解适用于方法,属性,类,表示不鼓励使用这种元素,通常很危险或者有更好的选择(已废弃)。
- @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。与前两个注解有所不同,需要传递一个参数才能正常使用,这些参数的值都是定义好的,选择使用即可。
@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)
@SuppressWarnings(value={“unchecked”,"deprecation"})
...
1.3 元注解
- 元注解的作用就是
负责注解其他注解
,java定义了4个标准的mata-annocation类型,它们被用来提供对其他annocation类型做说明。
@Target
,@Retention
,@Documented
,@Inherited
- @Target:用来描述注解的使用范围(描述注解可以作用在什么地方)
- @Retention:用于描述注解的生命周期(SOURCE<CLASS<RUNTIME(常用))
- @Documented:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
1.4 自定义一个注解
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析
- @interface用来声明一个注解,格式:public @interface 注解名{定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,定义注解元素时,经常使用空字符串,0作为默认值
//使用自定义注解,有默认值的可以不写
@MyAnnotation1(name = "张三",address = {"北京","杭州"})
@MyAnnotation2("李四")//只有一个参数时,参数名建议使用value,可以省略
public class test {
}
//表明该注解可以作用在类和方法上,在运行时有效
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation1{
String name();
int age() default 18;
String[] address();
}
//表明该注解可以作用在类和方法上,在运行时有效
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value();
}
反射机制
动态语言
- 是一类在运行时可以改变其结构的语言,可以增加一些新的函数,对象,可以改变已有的结构。通俗说就是在运行期间代码可以根据某些条0件改变自身结构
- 主要动态语言:C#、JavaScript、PHP、Python等。
静态语言
-
运行期间结构不可变的语言就是静态语言。C、C++、Java。
-
Java不是动态语言,但Java可以称之为“准动态语言”,即Java具有一定的动态性,可以利用
反射机制
实现类似动态语言
的特性。
2.1 Java反射机制概述
- Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API获得任何类的内部信息,并能直接操作任意对象的内部属性及方法
//获取该类的Class对象
Class c = Class.forName("java.lang.String")
加载完类之后
,在堆内存的方法区
中就产生了一个Class类型的对象(一个类只有一个Class对象
),这个对象就包含了完整的类的结构信息
,我们可以通过这个对象看到类的结构。- 这个Class对象就像一面镜子,透过这个镜子看到类的结构,称之为
反射
。
2.1.1 Java反射机制提供的功能
功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- …
2.1.2 Java反射的优缺点
优点
- 可以实现动态创建对象和编译,体现出很大的灵活性
缺点
- 对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它能满足我们的要求,这类操作总是慢于直接执行相同的操作的。
2.1.3 Java反射相关的主要API
API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- …
2.2 Class类概述
- 一个类在内存中只有一个Class对象
- 一个类被加载后,类的整个结构都会被封装在Class对象中
- 通过这个对象,可以获取这个类的所有东西
public class UserTest {
public static void main(String[] args) throws ClassNotFoundException {
//根据全限定类名获取该类的Class对象,forName是一个静态方法
Class c1 = Class.forName("cn.rm.pojo.User");
Class c2 = Class.forName("cn.rm.pojo.User");
System.out.println(c1.hashCode());//1735600054
System.out.println(c2.hashCode());//1735600054
}
}
Class类
通过反射获取一个类的class对象后可以得到的信息:这个类的属性,方法,构造器,这个类实现了哪些接口。对于某一个类而言,JRE都为其保留一个不变的Class类型的对象,一个Class对象包含了特定的某个结构的有关信息。
- Class本身也是一个类
- Class对象只能由系统创建对象
- 一个加载的类在JVM中只会有一个Class实例(单例模式)
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被加载的结构
- Class类是Reflection的根源,对于任意你想动态加载,运行的类,需要先获得对应的Class对象。
Class中常用的方法
2.3 获取Class类的实例对象
第一种
- 若已知具体的类,可以通过类的class属性直接获取,该方法最为安全可靠,程序性能最高
Class c = Person.class;
第二种
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class c = person.getClass();
第三种
- 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class c = Class.forName("cn.rm.pojo.Person");
第四种
- 内置基本数据类型的包装类可以直接用类名.Type
第五种
- 可以使用ClassLoader来获取
2.3.1 有Class对象的数据类型
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- [ ]:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
public class Test2 {
public static void main(String[] args) {
Class c1 = Object.class;
Class c2 = Runnable.class;
Class c3 = int[].class;
Class c4 = ElementType.class;
Class c5 = Override.class;
Class c6 = Integer.class;
Class c7 = void.class;
System.out.println(c1);//class java.lang.Object
System.out.println(c2);//interface java.lang.Runnable
System.out.println(c3);//class [I
System.out.println(c4);//class java.lang.annotation.ElementType
System.out.println(c5);//interface java.lang.Override
System.out.println(c6);//class java.lang.Integer
System.out.println(c7);//void
//只要元素类型与维度一致,就是同一个Class
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());//1735600054
System.out.println(b.getClass().hashCode());//1735600054
}
}
2.4 Java内存分析
堆
- 存放new出来的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区
- 可以被所有的线程共享
- 包含了所有的class的static变量
2.5 类的加载与ClassLoader
2.5.1 类的加载
类的加载过程
加载
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的Class对象
链接
- 将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
什么时候会发生类初始化
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
2.5.2 类加载器ClassLoader
类加载器的作用
-
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
-
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
类加载器的分类
根加载器(引导类加载器)
- 用C++编写,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取(…\java1.8\jre\lib\rt.jar)
扩展类加载器
- 负责jre/lib/ext目录下的jar包或java.ext.dirs指定目录下的jar包装入工作库
系统类加载器
- 负责java-classpath或java.class.path所指目录下的类与jar包装入工作库,是最常用的加载器。
获取类加载器
public class Test3 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//获取系统类加载器的父类扩展类加载器
ClassLoader parent = classLoader.getParent();
//获取扩展类加载器的父类根加载器
ClassLoader parent1 = parent.getParent();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@677327b6
System.out.println(parent1);//无法直接获取 null
//测试当前类是由哪个类加载器加载的(系统类加载器)
ClassLoader loader = Class.forName("cn.rm.test.Test3").getClassLoader();
System.out.println(loader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//测试JDK内置类是由哪个类加载器加载的(根加载器)
ClassLoader loader1 = Class.forName("java.lang.String").getClassLoader();
System.out.println(loader1);//null
//如何获得系统类加载器能加载的类路径
System.out.println(System.getProperty("java.class.path"));
//双亲委派机制(如果用户自定义一个类,包名和类名与系统类相同,
//会向上级类加载器中查询有无该类,如果有,用户自定义的类无效)
/*
F:\environment\java1.8\jre\lib\charsets.jar;
F:\environment\java1.8\jre\lib\deploy.jar;
F:\environment\java1.8\jre\lib\ext\access-bridge-64.jar;
F:\environment\java1.8\jre\lib\ext\cldrdata.jar;
F:\environment\java1.8\jre\lib\ext\dnsns.jar;
F:\environment\java1.8\jre\lib\ext\jaccess.jar;
F:\environment\java1.8\jre\lib\ext\jfxrt.jar;
F:\environment\java1.8\jre\lib\ext\localedata.jar;
F:\environment\java1.8\jre\lib\ext\mysql-connector-java-5.1.32.jar;
F:\environment\java1.8\jre\lib\ext\nashorn.jar;
F:\environment\java1.8\jre\lib\ext\sunec.jar;
F:\environment\java1.8\jre\lib\ext\sunjce_provider.jar;
F:\environment\java1.8\jre\lib\ext\sunmscapi.jar;
F:\environment\java1.8\jre\lib\ext\sunpkcs11.jar;
F:\environment\java1.8\jre\lib\ext\zipfs.jar;
F:\environment\java1.8\jre\lib\javaws.jar;
F:\environment\java1.8\jre\lib\jce.jar;
F:\environment\java1.8\jre\lib\jfr.jar;
F:\environment\java1.8\jre\lib\jfxswt.jar;
F:\environment\java1.8\jre\lib\jsse.jar;
F:\environment\java1.8\jre\lib\management-agent.jar;
F:\environment\java1.8\jre\lib\plugin.jar;
F:\environment\java1.8\jre\lib\resources.jar;
F:\environment\java1.8\jre\lib\rt.jar;
D:\IdeaProjects\annocationReflection\reflection\target\classes;
D:\software\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar
*/
}
}
2.6 获取运行时类的完整结构
通过反射获取运行时类的完整结构
包括:Field
、Method
、Constructor
、SuperClass
、Interface
、Annotation
。
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的属性
- 注解
public class Test4 {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//获取该类的Class对象
Class<Son> s = Son.class;
//获得该类的父类Class对象
Class superclass = s.getSuperclass();
System.out.println(superclass);
//获得该类的接口
Class[] interfaces = s.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);//interface java.lang.Runnable
}
//获得该类的方法
Method[] m= s.getMethods();//获得本类及父类的所有public方法
Method[] methods = s.getDeclaredMethods();//获取本类的所有方法(不包含父类)
for (Method method : methods) {
System.out.println(method);//包括父类的方法
}
//获取指定的方法需要传递参数类型,因为方法重载的存在
Method setName = s.getDeclaredMethod("setName", String.class);
System.out.println(setName);//public void cn.rm.test.Son.setName(java.lang.String)
//获得该类的属性
Field[] fields = s.getFields();//只能获得public修饰的属性
Field[] declaredFields = s.getDeclaredFields();//获得所有修饰符的属性
for (Field declaredField : declaredFields) {
System.out.println(declaredField);//private java.lang.String cn.rm.test.Son.name
}
}
}
class Father{
public void shout(){}
}
class Son extends Father implements Runnable{
private String name ="张三";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void run() {
}
public void eat(){
System.out.println("eat...");
}
}
2.6.1 类的实例化对象的作用
创建类的实例化对象:调用Class对象的newInstance()方法
- 类必须有一个无参构造器
- 类的构造器的访问权限需要足够
没有无参构造器的话
- 先获取有参构造器
- 向构造器中传递参数实例化对象
public class Test5 {
public static void main(String[] args) throws Exception {
//通过反射创建一个类的实例化对象
Class c1 = Class.forName("cn.rm.pojo.User");
User user1 = (User) c1.newInstance();
System.out.println(user1);//User{name='null', age=0}
//通过获取构造器,使用构造器创建一个对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("张三", 18);
System.out.println(user2);//User{name='张三', age=18}
//通过反射调用普通方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke激活(对象,方法的值)
setName.invoke(user1,"李四");
System.out.println(user1.getName());//李四
//通过反射操作属性
Field name = c1.getDeclaredField("name");
//不能直接操作private属性或者方法,如果需要操作,需要关闭安全检测
//关闭程序安全检测(属性和方法都可以调用该方法)
name.setAccessible(true);
name.set(user1,"王五");
System.out.println(user1.getName());//王五
}
}
性能检测测试
//性能测试
public class Test6 {
//普通方法创建对象,调用方法
public static void test1(){
User user1 = new User();
Long startTie = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user1.getName();
}
Long endTime = System.currentTimeMillis();
System.out.println("普通方式调用10亿次方法需要"+(endTime-startTie)+"ms");
}
//反射方法创建对象,调用方法
public static void test2() throws Exception {
User user2 = new User();
Class c2 = user2.getClass();
Method getName = c2.getDeclaredMethod("getName", null);
Long startTie = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user2,null);
}
Long endTime = System.currentTimeMillis();
System.out.println("反射方式调用10亿次方法需要"+(endTime-startTie)+"ms");
}
//反射方法创建对象,调用方法,关闭安全检测
public static void test3() throws Exception {
User user2 = new User();
Class c2 = user2.getClass();
Method getName = c2.getDeclaredMethod("getName",null);
getName.setAccessible(true);
Long startTie = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user2,null);
}
Long endTime = System.currentTimeMillis();
System.out.println("关闭安全检测之后反射方式调用10亿次方法需要"+(endTime-startTie)+"ms");
}
public static void main(String[] args) throws Exception {
test1();//普通方式调用10亿次方法需要3ms
test2();//反射方式调用10亿次方法需要1635ms
test3();//关闭安全检测之后反射方式调用10亿次方法需要1002ms
}
}
通过反射获取泛型
- 获取参数泛型
- 获取返回值泛型
public class Test7 {
public void method1(Map<String, User> map, List<String> list){
}
public static void main(String[] args) throws Exception {
//通过反射获取方法参数泛型
Method method1 = Test7.class.getDeclaredMethod("method1", Map.class, List.class);
Type[] types = method1.getGenericParameterTypes();
for (Type type : types) {
if(type instanceof ParameterizedType){
//获得真实参数类型
Type[] actualTypeArgs = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualTypeArg : actualTypeArgs) {
System.out.println(actualTypeArg);
/*
class java.lang.String
class cn.rm.pojo.User
class java.lang.String
*/
}
}
}
}
}
2.7 调用运行时类的指定结构
通过反射操作注解
//使用反射操作注解,实现ORM(对象关系映射)
public class Test8 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.rm.test.People");
//获取注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的值
Table annotation = (Table) c.getAnnotation(Table.class);
System.out.println(annotation.value());//db_people
//获得属性上的注解的值
Field id = c.getDeclaredField("id");
Filed annotation1 = id.getAnnotation(Filed.class);
System.out.println(annotation1.colName());//db_id
System.out.println(annotation1.type());//int
System.out.println(annotation1.length());//20
}
}
@Table("db_people")
class People{
@Filed(colName = "db_id",type = "int",length = 20)
private int id;
@Filed(colName = "db_name",type = "varchar",length = 20)
private String name;
@Filed(colName = "db_age",type = "int",length = 3)
private int age;
}
//创建一个作用在类上的注解(对应数据库中的一个表)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
String value();
}
//创建一个作用在属性上的注解(对应数据库中的每个字段)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
//对应数据库中的一个字段(字段名,类型,大小)
String colName();
String type();
int length();
}
多线程
- 线程就是独立的执行路径
- 在程序运行时,即使没有创建线程,后台也会有多个线程,如主线程,gc线程
- main()称为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能干预的
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
3.1 线程的三种创建方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
3.1.1 继承Thread类
- 自定义类继承Thread类
- 重写run方法,编写线程执行体
- 创建线程对象,调用start()方法开启线程
run()
方法体是线程执行内容,start()
方法才是开启线程的方法。
多线程下载网络上的图片,导入import org.apache.commons.io包
public class Demo1 extends Thread{
private String url;//网络图片地址
private String name; //文件名
public Demo1(){
}
//构造方法
public Demo1(String url,String name){
this.url=url;
this.name=name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.down(url,name);
System.out.println("下载了文件名为"+name);
}
public static void main(String[] args) {
Demo1 d1 = new Demo1("https://xxx/xx.jpg","b.jpg");
Demo1 d2 = new Demo1("https://xx/xxx.jpg","d.jpg");
Demo1 d3 = new Demo1("https://x/xxxx.jpg","c.jpg");
d1.start();
d2.start();
d3.start();
}
//下载器
class WebDownloader{
//下载方法
public void down (String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.1.2 实现Runnable接口
- 推荐实现Runnable接口开启多线程,因为Java单继承的局限性
- 定义一个类实现Runnable接口
- 实现run方法,编写线程执行体
- 创建线程对象,调用start方法执行
public class Demo1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println("多线程执行体");
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
//开启线程,代理模式
new Thread(demo1).start();
for (int i = 0; i < 2000; i++) {
System.out.println("主线程执行体");
}
}
}
开启多线程的两种方式区别
继承Thread类
- 启动线程:子类对象.start();
- 不建议使用,避免OOP单继承局限性
实现Runnable接口
- 启动线程:new Thread(接口实现类对象)start();
- 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用。
3.1.3 实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务
ExecutorsService service= Executors.newFixedThreadPool(1);
- 提交执行
Future< Boolean > s1 = service.submit(d1);
- 获取结果
Boolean ss1 = s1.get();
- 关闭服务
service.shutdownNow();
实现Callable多线程下载图片
public class Demo1 implements Callable<Boolean> {
private String url;//网络图片地址
private String name; //文件名
public Demo1(){
}
//构造方法
public Demo1(String url,String name){
this.url=url;
this.name=name;
}
//下载图片线程的执行体
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.down(url,name);
System.out.println("下载了文件名为"+name);
return true;
}
public static void main(String[] args) throws Exception {
Demo1 d1 = new Demo1("https://xxx/x.jpg","b.jpg");
Demo1 d2 = new Demo1("https://xx.jpg","d.jpg");
Demo1 d3 = new Demo1("https://xxx.jpg","c.jpg");
//- 创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//- 提交执行
Future<Boolean> s1 = service.submit(d1);
Future<Boolean> s2 = service.submit(d2);
Future<Boolean> s3 = service.submit(d3);
//- 获取结果
Boolean ss1 = s1.get();
Boolean ss2 = s2.get();
Boolean ss3 = s3.get();
//- 关闭服务ser.shutdownNow();
service.shutdownNow();
}
//下载器
class WebDownloader{
//下载方法
public void down (String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
实现Callable接口的好处
- 可以有返回值
- 可以抛出异常
3.2 并发问题
- 多个线程同时操作同一资源时,线程不安全
买票问题
public class Demo1 implements Runnable {
private int ticket =10;
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}else {
System.out.println(Thread.currentThread().getName()+
"拿到了第"+ticket--+"张票");
}
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
new Thread(demo1,"学生").start();
new Thread(demo1,"老师").start();
new Thread(demo1,"黄牛").start();
}
}
多线程实现龟兔赛跑
public class Demo2 implements Runnable{
private String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子") || i%10==1){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(gameover(i)){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了--->"+i);
}
}
public boolean gameover(int steps){
if(winner != null){
return true;
}else if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner是"+winner);
return true;
}
return false;
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
new Thread(demo2,"乌龟").start();
new Thread(demo2,"兔子").start();
}
}
3.3 静态代理模式
多线程的底部实现原理就是静态代理模式
- 真实对象和代理对象都有实现同一接口
- 代理对象要代理真实角色
好处
- 代理对象可以做很多真实对象做不了的事情
- 真实对象只需要专注于自己的事情
public class People implements Wedding{
private String name;
public People(String name) {
this.name = name;
}
@Override
public void wedding() {
System.out.println(this.name+"要结婚了");
}
public static void main(String[] args) {
//通过代理对象实现结婚
People people = new People("张三");
proxy proxy = new proxy(people);
proxy.wedding();
}
}
//代理类
class proxy implements Wedding{
private People p;
public proxy(People p){
this.p=p;
}
@Override
public void wedding() {
System.out.println("婚前准备工作");
p.wedding();
System.out.println("婚后准备工作");
}
}
3.4 Lambda表达式
- 避免匿名内部类定义过多
- 实质属于函数式编程
- 去掉一堆没意义的代码,只留下核心的逻辑
函数式接口
- 任何接口,如果只包含唯一的抽象方法,那么他就是一个函数式接口
- 对于函数式接口,可以通过lambda表达式来创建该接口的对象
public class Test01 {
//定义一个方法startThread,方法的参数使用函数式接口Runnable
public static void startThread(Runnable run){
//开启多线程
new Thread(run).start();
}
public static void main(String[] args) {
//调用startThread方法,方法的参数是一个接口,可以传递这个接口的匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程启动了");
}
});
//调用startThread方法,方法的参数是一个函数式接口,可以传递Lambda表达式
startThread(()-> System.out.println(
Thread.currentThread().getName()+"线程启动了"));
}
}
3.5 线程的五大状态
创建
、就绪
、阻塞
、运行
、死亡
。
3.6 线程方法
- setPriority(int newPriority) 更改线程的优先级
- static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
- void join() 等待该线程终止(插队)
- static void yield()暂停当前正在执行的线程对象,并执行其他线程(礼让)
- void interrupt()中断线程,别用这个方式
- boolean isAlive()测试线程是否处于活动状态
3.6.1 停止线程
- 不推荐使用JDK提供的stop()、destory()方法。【已废弃】
- 推荐线程自己停止下来。
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
public class Demo3Test implements Runnable{
//定义一个标志
private boolean flag = true;
@Override
public void run() {
while (flag){
//线程体
}
}
//对外提供一个方法,调用该方法时,线程会自动停止
public void stop(){
this.flag = false;
}
}
3.6.2 线程休眠
- sleep(毫秒数)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态,可能立即执行,也可能先执行其他线程
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
模拟网络延时:
放大问题的可能发生性
//模拟倒计时
public class Demo4 implements Runnable{
@Override
public void run() {
for (int i = 20; i > 0; i--) {
try {
Thread.sleep(1000);
System.out.println("倒计时"+i+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new Demo4()).start();
}
}
3.6.3 线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞。
- 将线程从运行状态转为就绪状态。
- 让cpu重新调度,礼让不一定成功,由CPU决定。
public class Demo5TestYield implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(Thread.currentThread().getName().equals("张三")){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"正在走第"+i+"步");
}
}
public static void main(String[] args) {
new Thread(new Demo5TestYield(),"张三").start();
new Thread(new Demo5TestYield(),"李四").start();
}
}
3.6.4 线程合并
- join() 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
- 可以想象成插队。
- 减少使用该方法,会造成阻塞。
public class Demo6TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("vip线程"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Demo6TestJoin());
thread.start();
for (int i = 0; i < 300; i++) {
if(i==200){
thread.join();
}
System.out.println("main"+i);
}
}
}
3.7 线程状态观测
Thread.State
线程状态,线程可以处于以下状态之一
- NEW 尚未启动的线程处于此状态。
- RUNNABLE 在JAVA虚拟机中执行的线程处于此状态。
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
- WAITING正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED已退出的线程处于此状态。
一个线程在给定时间点处于某一状态,这些状态是不反映任何操作系统线程状态的虚拟机状态。
public class Demo7TestState {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("............");
});
//获取线程的状态
Thread.State state = t.getState();
System.out.println(state);
t.start();
state= t.getState(); //不用每次都创建一个新的对象,节省内存空间
System.out.println(state);
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(1000);
state = t.getState();
System.out.println(state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 死亡之后的线程不能被再次启动
3.8 线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1-10,超出该范围会报错
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
- 使用以下方式改变或获取优先级:getPriority() setPriority(int n);
先设置优先级,再启动线程
- 优先级低只是意味着获取调度的概率低,并不是优先级低就不会被调用了,主要还是看CPU的调度
public class Demo8TestPriority {
public static void main(String[] args) {
//获取主线程的名字和优先级
System.out.println(Thread.currentThread().getName()+"--->"+
Thread.currentThread().getPriority());
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t4 = new Thread(myRunnable);
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(1);
t4.setPriority(8);
t1.start();
t2.start();
t4.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
//获取当前正在执行的线程的名字和优先级
System.out.println(Thread.currentThread().getName()+"--->"+
Thread.currentThread().getPriority());
}
}
3.9 守护(daemon)线程
- 线程分为
用户线程
和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,监控内存,垃圾回收等待
public class Demo9TestDaemon {
public static void main(String[] args) {
God god = new God();
Person person = new Person();
//将god线程设置为守护线程,用户线程结束后,守护线程会自动结束
Thread godThread = new Thread(god);
godThread.setDaemon(true);//默认是false(用户线程)
godThread.start();
new Thread(person).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你");
}
}
}
class Person implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3650; i++) {
System.out.println("你一直开心的活着");
}
System.out.println("======GOOD BYE ! WORLD======");
}
}
3.10 线程同步
并发
- 多个线程操作同一对象。
- 处理多线程问题时,多个线程访问同一对象,并且某些线程还想修改这个对象,这时就需要线程同步,线程同步其实就是一个等待机制,多个需要同时访问此对象的线程进入这个
对象的等待池
形成队列,等待前面线程使用完毕,下一个线程再使用。 - 形成条件:队列+锁
线程同步
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。