注解

定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

作用分类:

①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】

② 代码分析:通过代码里标识的注解对代码进行分析【使用反射】

③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

  • @Retention: 表示注解的生命周期

RetentionPolicy.SOURCE – 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码 RetentionPolicy.CLASS – 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。

RetentionPolicy.RUNTIME– 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

  • @Target: 表示注解能够使用的位置(常用的位置有类、方法、字段、方法参数)

ElementType.TYPE - 类型

ElementType.FIELD - 字段

ElementType.METHOD - 方法

ElementType.PARAMETER - 参数

ElementType.CONSTRUCTOR - 构造方法

ElementType.LOCAL_VARIABLE - 局部变量

ElementType.ANNOTATION_TYPE - 注解

ElementType.PACKAGE - 包


  • 使用注解完成包的扫描
package com.wz.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//value属性是可以省略的
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)//生命周期
public @interface ScanPackage {
    /**
     * 要扫描的包,默认值为空,如果不给值,就默认扫描这个注解
     * 所标记的类所在的包
     * @return
     */
    String value() default "";
}
package com.wz.anno;

import java.io.File;
import java.net.URL;

//使用注解
@ScanPackage("com")
public class AnnoTest {
    public static void main(String[] args) {
        Class<AnnoTest> clazz = AnnoTest.class;
        //获取类上使用的ScanPackage注解
        ScanPackage scanPackage = clazz.getAnnotation(ScanPackage.class);
        if (scanPackage != null){
            //获取要扫描的包
            String pk = scanPackage.value();
            if ("".equals(pk)){//如果要扫描的包为空字符串,说明没有给定扫描的包
                //要扫描的包就是ScanPackage注解标记的类所在的包
                pk = clazz.getPackage().getName();
                //com.wz.anno
            }
            //包的本质就是文件夹,要将包变成文件夹,只需要将包名中的"."替换成"/"
            String path = pk.replace(".","/");
            //相对路径转换位可以定位的文件路径
            //使用到类加载器来实现
            ClassLoader classLoader = clazz.getClassLoader();
            //利用类加载器获取文件的定位信息
            URL url = classLoader.getResource(path);
            if (url!=null){
                File folder = new File(url.getPath());
                //D:\Project%20file\fyOfflineCourse\MyDay10\out\production\MyDay10\com\wz\anno
                scanFolder(folder,pk);
            }

        }
    }
    private static void scanFolder(File folder, String pk){
        File[] files = folder.listFiles(file -> file.getName().endsWith(".class") || file.isDirectory());
        if(files != null){
            for (File file : files) {
                if(file.isDirectory()){
                    scanFolder(file, pk + "." + file.getName());//递归扫描
                } else {
                    String simpleName = file.getName();//类名
                    int index = simpleName.indexOf('.');
                    simpleName = simpleName.substring(0, index);
                    String className = pk + "." + simpleName;
                    System.out.println(className);
                }
            }
        }
    }
}
  • 使用注解完成对象属性的赋值
package com.wz.anno03;

import java.lang.reflect.Field;

public class RejectionValueTest {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Class<Teacher> clazz = Teacher.class;
        Teacher teacher = clazz.newInstance();
        //获取字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Value value = field.getAnnotation(Value.class);
            if (value!=null){
                String defaultValue = value.value();
                if ("".equals(defaultValue)){
                    throw new RuntimeException("属性不能为空");
                }
                field.setAccessible(true);
                //获取字段的类型
                Class<?> fieldType = field.getType();
                if(fieldType == String.class){
                    field.set(teacher, defaultValue);
                } else if(fieldType.isPrimitive()){//如果字段类型是基本数据类型(包含了包装类)
                    if(fieldType == int.class || fieldType == Integer.class){
                        field.set(teacher, Integer.parseInt(defaultValue));
                    } else if(fieldType == short.class || fieldType == Short.class){
                        field.set(teacher, Short.parseShort(defaultValue));
                    } else if(fieldType == byte.class || fieldType == Byte.class){
                        field.set(teacher, Byte.parseByte(defaultValue));
                    } else if(fieldType == float.class || fieldType == Float.class){
                        field.set(teacher, Float.parseFloat(defaultValue));
                    } else if(fieldType == long.class || fieldType == Long.class){
                        field.set(teacher, Long.parseLong(defaultValue));
                    } else if(fieldType == double.class || fieldType == Double.class){
                        field.set(teacher, Double.parseDouble(defaultValue));
                    } else if(fieldType == boolean.class || fieldType == Boolean.class){
                        field.set(teacher, Boolean.parseBoolean(defaultValue));
                    } else if(fieldType == char.class || fieldType == Character.class){
                        field.set(teacher, defaultValue.charAt(0));
                    }
                }
            }
        }
        System.out.println(teacher);
    }
}
package com.wz.anno03;

public class Teacher {
    @Value("ZhangSan")
    String name;
    @Value("10000")
    double salary;

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
package com.wz.anno03;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {

    String value() default "";

}

ThreadLocal

1. 基本使用

ThreadLocal 是用来为当前线程维护的一个局部变量,这个维护的局部变量只有当前线程可以访问。ThreadLocal底层采用的是ThreadLocalMap来存储维护的数据。ThreadLocalMap 相当于一个Hashmap,每一个线程自身都会维护一个 ThreadLocalMap 对象(受包保护),因为这个ThreadLocalMap 中存储的数据就只有维护的线程才能访问。

  • ThreadLocal的作用:

线程并发:在多线程环境下使用

传递数据:可以通过ThreadLocal在同一线程,不同组件中传递数据

线程隔离:每个线程的变量都是独立的,不会相互影响

  • 常用方法

方法声明

描述

ThreadLocal()

创建ThreadLocal对象

public void set(T value)

设置当前线程绑定的局部变量

public T get()

获取当前线程绑定的局部变量

public void remove

移除当前线程绑定的局部变量

  • ThreadLocal基本使用
package com.wz.threadlocal01;

public class MyDemo {

    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

不使用ThreadLocal(无线程隔离)

package com.wz.threadlocal01;

public class ThreadLocalTest {

    public static void main(String[] args) {
        MyDemo demo = new MyDemo();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName()+"的数据");
                    System.out.println("-----------------------");
                    System.out.println(Thread.currentThread().getName()+"---->"+demo.getContent());
                }
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}

结果:

2023年7月22日,注解,ThreadLocal​​_注解

使用ThreadLocal(线程隔离)

package com.wz.threadlocal01;

public class MyDemo {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private String content;

    public String getContent() {
        return threadLocal.get();
    }

    public void setContent(String content) {
        threadLocal.set(content);
    }
}

MyDemo类中,threadLocal是一个ThreadLocal<String>类型的成员变量。ThreadLocal类是Java提供的一个特殊的类,可以创建线程局部变量。每个线程都可以独立地访问自己的线程局部变量,而不会与其他线程的线程局部变量产生冲突。

MyDemo类还有一个私有的content成员变量,它用于保存内容。

getContent()方法用于获取线程局部变量的值,它通过调用threadLocal.get()来实现。

setContent(String content)方法用于设置线程局部变量的值,它通过调用threadLocal.set(content)来实现。


结果:

2023年7月22日,注解,ThreadLocal​​_ThreadLocal_02

2. TheadLocalsynchronized 区别

  • 使用synchronized
package com.wz.threadlocal01;

public class ThreadLocalTest {

    public static void main(String[] args) {
        MyDemo demo = new MyDemo();
        Object o = new Object();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (o){
                        demo.setContent(Thread.currentThread().getName()+"的数据");
                        System.out.println("-----------------------");
                        System.out.println(Thread.currentThread().getName()+"---->"+demo.getContent());
                    }
                }
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}

结果:

2023年7月22日,注解,ThreadLocal​​_ThreadLocal_03

使用synchronized 虽然实现了线程隔离,但是牺牲了线程的并发执行的时间,以时间换空间


synchronized

ThreadLocal

原理

同步机制采用‘以时间换空间’的方式,只提供了一份变量,让不同线程排队访问

ThreadLoacl采用‘以空间换时间’的方式,为每一个线程都提供了一份变量副本,从而实现同时访问而不互相干扰

侧重点

多个线程之间访问资源的同步

多线程中让每个线程之间的数据相互隔离