多线程
线程(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);