多线程、泛型与反射


多线程


线程(Thread)是进程(Process,正在运行的程序的实例)中的单一顺序的控制流。多个线程之间共享内存,是同一个进程的不同「支线」。

「多线程」即在一个程序中启动多个线程,表现为多个任务「平行」运行。

启动新线程
首先需要将待启动的新线程放在一个 Thread 类的 run() 方法中,有两个方法创建这种类:

建立 Thread 类的子类。
实现 Runnable 接口。更常用,原因:
任务与运行解耦。
更容易实现多线程资源共享。
避免单继承的局限性。
然后调用这个类的 start() 方法,将立即返回,同时线程启动开始运行。不能直接调用它的 run() 方法。

// 先做一个类,实现 Runnable 接口
class MyThread implements Runnable {
        // 把要做的事情放在 run() 方法中
        @Override
        public void run() {
                // 做点什么
        }
        public MyThread() {
                super();
        }
}
// 实例化一个对象,然后启动
Thread t = new MyThread();
t.start();
在 Java 8 后用 lambda 表达式可以简写为

Thread t = new Thread(() -> {
        // 做点什么
});
t.start();
线程的停止
线程可以被自发地停止。

run() 方法正常退出,线程自然终止。
run() 方法中发生异常而没有被捕获,线程意外终止。
调用线程的 stop() 方法可以在外部终止一个线程。不安全,已经废弃。

线程的状态
在 Java 中线程有 5 种状态,分别是:

新建状态 NEW,线程创建但没有 start() 的状态。

可运行状态 RUNNABLE,线程已经启动。

就绪状态 READY,线程正在等待资源。
运行状态 RUNNING,线程正占用资源运行。
阻塞状态 BLOCKED,当遇到 synchorized 或者 lock 且没有得到相应的锁,则进入这个状态。

等待状态 WAITING,在当前线程调用 Object.wait() 或者其他线程调用 Thread.join() 且没有设置时间时,进入等待状态。

当调用了 Thread.join() 的线程结束,或调用 Object.notify() 或 Object.notifyAll() 来唤醒等待中的线程时,线程返回可运行状态。

计时等待状态 TIMED_WAITING,在当前线程调用 Thread.sleep(time) 或者当前线程调用 Object.wait(time) 或者其他线程调用 Thread.join(time) 时,进入该状态。

在计时结束后,线程返回可运行状态。

停止状态 TERMINATED,线程已经停止。

线程的中断
对一个线程调用 interrupt() 方法可以将线程的中断标记设置为 true。线程可以自己决定是否处理这个中断。

同步与死锁
在 Java 中使用 synchronized 关键字对一个对象进行加锁。加锁后,这个对象在解锁前都会被当前的线程独占。

class Counter {
        public static int count = 0;
}

/** 某线程的 run() 方法 **/
for (int i = 0; i < 100; i++) {
        synchronized (Counter.lock) { // 获得锁
                // JVM 将保证在这里的代码执行期间,Counter 不会被其他线程改变
                Counter.lock++;
        } // 解除锁
}
如果多个线程获得锁的顺序不一致,可能会出现「死锁」,即多方进入一个「你锁了 A,我锁了 B,我得不到 A,你得不到 B」的僵持状态。通过控制各个线程获得锁的顺序可以避免死锁。

生产者—消费者模式
![Untitled](%E5%A4%9A%E7%BA%BF%E7%A8%8B%E3%80%81%E6%B3%9B%E5%9E%8B%E4%B8%8E%E5%8F%8D%E5%B0%84%206e572eeb16b3446893407c2aefcd0d6b/Untitled.png)

泛型


泛型使得一个类 / 方法 / 接口可以针对不同的数据类型进行复用,从而提升代码的复用率。

泛型类
泛型类就是具有一个或多个泛型变量的类。

class Generic<T> { // 声明这个类的泛型 T
        // 现在可以把 T 当作和 Integer、String 一样的数据类型了
        public T t;
        public Generic() { ... }
        public Generic(T t) { ... }
        public void func1(T t) { ... }
        public T func1() { ... }
}
我们需要在 new 泛型类的时候指定它用的具体类型。

// 特别注意在泛型类中不能用基本数据类型,即不能用 int 只能用 Integer
Generic<Integer> g1 = new Generic<>();
g1.t = 114514; // 没问题
g1.t = "114514"; // 大问题
一个泛型类可以引入多个泛型。

class Generic<T, E> { ... }
泛型方法
泛型方法就是有一个或多个泛型变量的方法。它可以定义在普通类中,也可以定义在泛型类中。泛型类中用了泛型变量的方法不是泛型方法(如上面的 func1(T t)),只有声明泛型的方法才是「泛型方法」。

class GenericFunction {
        public <T, E> void func(T t, E e) { ... }
        public <T> T func2() { ... }
}
在调用泛型方法时,可以不在方法名前指明泛型的具体类型。

GenericFunction g = new GenericFunction();
g.<Integer, String>func(123, "123");
g.func2("hi");
静态的泛型方法如果出现在一个泛型类中,它并不能使用泛型类的泛型。

泛型接口
接口也可以有泛型。在实现泛型接口时,实现的类既可以是泛型类,也可以是非泛型类。

interface Addable <T> {
        public T add(T a, T b);
}

// 当实现为非泛型类时,要指定泛型的具体类型
class Int implements Addable<Integer> {
        @Override
        public Integer add(Integer a, Integer b) { return a + b; }
}

// 当实现为泛型类时,前后要一致
//           V---------------------V
class Number<T> implements Addable<T> {
        @Override
        public T add(T a, T b) { ... }
}
通配符
泛型是不考虑类与类的继承关系的。即,尽管 Integer 是 Object 的子类,但我们不能把 List<Integer> 代入形参 List<Object>。

// 某个方法的声明
void func(List<Object> list) { ... }

List<Integer> intList = ...;
func(intList); // 大问题,会报错
相应地,我们要使用泛型的通配符。

<?> 可以匹配任何类型。
<? extends class> 可以匹配 class 类和它的所有子类。
<? super class> 可以匹配 class 类和它的所有超类。
通配符只能用于形参和调用代码,不能用来定义类和泛型方法!

public void func(List<? extends Number> list) { ... } // 没问题
public <? extends Number> void func(List<?> list) { ... } // 大问题


反射

Class 类
Class 类是「类的类」,一个 Class 对象称为「类的类型对象」,由 JVM 在每个类实例化时产生。

Student hans = new Student("Hans", 19); // 创建一个 Student 对象
System.out.println(hans.getClass()); // 打印 Class Student
System.out.println(hans.getClass().getName()); // 打印 Student
上面的例子展示了,对任一对象使用 getClass() 方法都可以得到它的类的类型对象。另外,我们也可以用这样的方法根据「类名」获得一个类的类型对象:

Class cl = Class.forName("Student"); // forName() 静态方法去查找一个名字叫 Student 的类
System.out.println(cl); // 打印 Class Student
如果 forName() 静态方法没能找到对应的类,会抛出 ClassNotFoundException 异常。

使用反射创建类的实例
使用 Class.newInstance(),例如

  Class cl = Class.forName("Date"); // 获得 Date 类的类型对象
  Date date = cl.newInstance() 
这会调用类默认的构造函数(无参)。

先使用 Constructor 得到一个类的构造方法,然后调用。

  // 获取所有「公有」的构造方法
  public Constructor[] getConstructors() {}
  // 获取所有的构造方法
  public Constructor[] getDeclaredConstructors() {}
  // 获取指定类型的「公有」的构造方法
  public Constructor getConstructor(Class ...) {}
  // 获取指定类型的所有的构造方法
  public Constructor getDeclaredConstructor(Class ...) {}
例如

  public class Student {
          private String name;
          public Student() { ... } // 1
          public Student(String name) { ... } // 2
  }

  Class StudentClass = Class.forName("Student");
  Constructor con = StudentClass.getConstructor(String.class) // 会获得 2
  Constructor[] cons = StudentClass.getConstructors(); // 会获得 1 和 2

  Student stu = (Student) con.newInstance("Hans"); // 调用 newInstance() 来创建实例
使用反射获取和修改成员变量
我们可以通过反射的方式获得和修改一个对象中的任意字段的值,无论这个字段是否公开。

// 获取所有公有的字段
public Field[] getFields() {}
// 获取所有的字段
public Field[] getDeclaredFields() {}
// 获取指定名字的公有的字段
public Field getField(String name) {}
// 获取指定名字的字段
public Field getDeclaredField(String name) {}

// 获得字段的值
Field.get(Object object)
// 修改字段的值
Field.set(Object object, Object value)
例如

Student hans = new Student("Hans", 19);
// 获得 Student 类的类型类
Class studentClass = hans.getClass();
// 获得 Student 类的 name 字段
Field f = studentClass.getDeclaredField("name");
// 获得 Student 对象 hans 的 name 字段的值
f.setAccessable();
String hansName = (String) f.get(hans);
// 改变 Student 对象 hans 的 name 字段的值
f.set(hans, "Hans WAN");
使用反射获得成员方法
我们可以通过反射的方式获得一个对象中的任意方法,无论是否公开。

// 获取所有的公有方法
public Method[] getMethods() {}
// 获取所有的成员方法,无论公开与否
public Method[] getDeclaredMethods() {}
// 获取指定方法名和参数类型的方法
public Method[] getMethod(String name, Class ...) {}

// 调用获得的方法
Method.invoke(Object object, Object ...)
例如

Student hans = new Student("Hans", 19);
Method m = Student.class.getMethod("getName", String.class, Integer.class);
String hansName = (String) m.invoke(hans);

猜你喜欢

转载自blog.csdn.net/qq_62940532/article/details/130340840