文章目录
泛型
PECS 原则
何时使用extends
,何时使用super
?为了便于记忆,我们可以用PECS原则:Producer Extends Consumer Super。
即:如果需要返回T
,它是生产者(Producer),要使用extends
通配符;如果需要写入T
,它是消费者(Consumer),要使用super
通配符。
以Collections
的copy()
方法为例
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i); // src是producer
dest.add(t); // dest是consumer
}
}
}
需要返回T
的src
是生产者,因此声明为List<? extends T>
,需要写入T
的dest
是消费者,因此声明为List<? super T>
。
异常处理
异常的体系结构
Throwable是异常体系的根,Error和Exception继承自它
Error表示严重的错误,是由JVM产生和抛出的,无需我们处理。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
java规定:
- 必须捕获的异常,包括
Exception
及其子类,但不包括RuntimeException
及其子类,这种类型的异常称为Checked Exception
。 - 不需要捕获的异常,包括
Error
及其子类,RuntimeException
及其子类。
反射
Java的反射是指程序在运行期可以拿到一个对象的所有信息。
Class类
除了 int
等基本类型外,Java的其他类型全部都是class
(包括interface
)。
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。每加载一种class
,JVM就为其创建一个Class
类型的实例,并关联起来
获取一个class
的Class
实例的三种方法:
- Class.forName(“全类名”);
- Hero.class;
- new Hero().getClass();
JVM为每个加载的class
及interface
创建了对应的Class
实例来保存class
及interface
的所有信息;
获取一个class
对应的Class
实例后,就可以获取该class
的所有信息;
访问字段
Java的反射API提供的Field
类封装了字段的所有信息,但是通过反射读写字段是一种非常规方法,它会破坏对象的封装。
通过Class
实例的方法可以获取Field
实例
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
public class Test {
public static void main(String[] args) throws Exception {
Class<Student> studentClass = Student.class;
// 获取public字段"score":
System.out.println(studentClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(studentClass.getField("name"));
// 获取private字段"grade":
System.out.println(studentClass.getDeclaredField("grade"));
}
}
class Student extends Person {
public int score;
private int grade;
}
class Person {
public String name;
}
通过上述代码运行结果如下所示
public int cn.akangaroo.reflection.Student.score
public java.lang.String cn.akangaroo.reflection.Person.name
private int cn.akangaroo.reflection.Student.grade
一个Field
对象包含了一个字段的所有信息:
getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
以String
类的value
字段为例,它的定义是:
public final class String {
private final byte[] value;
}
我们用反射获取该字段的信息,代码如下:
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)
来访问非public
字段。
public class Test {
public static void main(String[] args) throws Exception {
// 获取Class实例
Class<Person> personClass = Person.class;
// 创建一个新的person对象
Person p = new Person();
// 获取public String name 字段
Field name = personClass.getField("name");
name.set(p, "hehe");
System.out.println(p.name);
// 获取private int age 字段
Field age = personClass.getDeclaredField("age");
// 因为是private的,所以需要设置setAccessible(true);
age.setAccessible(true);
age.set(p, 12);
// 因为是private的,用反射获取对象的值。
System.out.println(age.get(p));
}
}
class Person {
public String name;
private int age;
}
调用方法
Java的反射API提供的Method对象封装了方法的所有信息:
通过Class
实例的方法可以获取Method
实例:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
public class Test {
public static void main(String[] args) throws Exception {
Class<Student> studentClass = Student.class;
// 获取public方法getScore,参数为String:
System.out.println(studentClass.getMethod("getScore", String.class));
// 获取继承的public方法getName,无参数:
System.out.println(studentClass.getMethod("getName"));
// 获取private方法getGrade,参数为int:
System.out.println(studentClass.getDeclaredMethod("getGrade", int.class));
}
}
class Student extends Person {
public int getScore(String type) {
return 99;
}
private int getGrade(int year) {
return 1;
}
}
class Person {
public String getName() {
return "Person";
}
}
运行结果:
public int cn.akangaroo.reflection.Student.getScore(java.lang.String)
public java.lang.String cn.akangaroo.reflection.Person.getName()
private int cn.akangaroo.reflection.Student.getGrade(int)
通过Method
实例可以获取方法信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
通过Method
实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)
;
通过设置setAccessible(true)
来访问非public
方法;
public class Test {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
Person person = new Person();
// 调用普通方法
Method normalMethod = personClass.getMethod("setName", String.class);
String name = (String) normalMethod.invoke(person, "张三");
System.out.println(name);
// 调用静态方法
Method staticMethod = personClass.getMethod("getNum");
Integer num = (Integer) staticMethod.invoke(null);
System.out.println(num);
// 调用非public方法
Method privateMethod = personClass.getDeclaredMethod("getName");
privateMethod.setAccessible(true);
String name1 = (String) privateMethod.invoke(person);
System.out.println(name1);
}
}
class Person {
public String name;
public String setName(String name) {
this.name = name;
return this.name;
}
public static Integer getNum() {
return 1024;
}
private String getName() {
return this.name;
}
}
通过反射调用方法时,仍然遵循多态原则。
public class Test {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
Method m = personClass.getMethod("setAge", Integer.class);
Integer age = (Integer) m.invoke(new Student(), 20);
System.out.println(age);
}
}
class Student extends Person {
public Integer age;
public Integer setAge(Integer age) {
this.age = age + 1;
return this.age;
}
}
class Person {
public Integer age;
public Integer setAge(Integer age) {
this.age = age;
return this.age;
}
}
调用构造方法
一般是使用new
来创建一个对象;
如果使用反射来创建对象,可以调用Class提供的newInstance()
方法,但是它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
Person person = Person.class.newInstance();
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以通过Constructor
实例创建一个实例对象:newInstance(Object... parameters)
; 通过设置setAccessible(true)
来访问非public
构造方法。
通过Class
实例的方法可以获取Constructor
实例:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
public class Test {
public static void main(String[] args) throws Exception {
Constructor<Person> constructor = Person.class.getConstructor(Integer.class);
Person person = constructor.newInstance(17);
System.out.println(person.name + "~~" + person.age);
Constructor<Person> constructor1 = Person.class.getConstructor(Integer.class, String.class);
Person person1 = constructor1.newInstance(18, "张三");
System.out.println(person1.name + "~~" + person1.age);
}
}
class Person {
public Integer age;
public String name;
public Person(Integer age) {
this.age = age;
}
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
}
多线程基础
创建线程
Java用Thread
对象表示一个线程,通过调用start()
启动一个新线程;
-
继承Thread类
public class Test { public static void main(String[] args) { Thread thread = new MyThread(); thread.start(); } } class MyThread extends Thread { @Override public void run() { System.out.println("启动一个线程"); } }
-
实现Runnable接口
public class Test { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("启动一个线程"); } }
Java8可以使用lambda表达式创建
public class Test { public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("haha~~"); }, "A"); t1.start(); } }
-
实现Callable接口,对于Callable的说明,详见JUC
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask(new MyCallable()); Thread t = new Thread(futureTask); t.start(); System.out.println(futureTask.get()); } } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("启动一个线程"); return 1024; } } 运行结果: 启动一个线程 1024
对于Callable和Runnable,有三点不同之处:
- 有无返回值
- 是否可以抛出异常
- call()方法和run()方法
线程状态
下面是java.lang.Thread.State
枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()
方法获取当前线程的状态。
线程状态 | 解释 |
---|---|
NEW | 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。 |
RUNNABLE | 就绪状态(调用start,等待调度)+ 正在运行 |
BLOCKED | 等待监视器锁时,陷入阻塞状态 |
WAITING | 等待状态的线程正在等待另一线程执行特定的操作(如notify) |
TIMED_WAITING | 具有指定等待时间的等待状态 |
TERMINATED | 线程完成执行,终止状态 |
下图源自《Java并发编程艺术》图4-1
常见线程方法
关键字 | 简介 |
---|---|
sleep | 当前线程暂停 |
join | 加入到当前线程中 |
setPriority | 设置线程优先级 |
yield | 临时暂停 |
setDaemon | 守护线程 |
sleep
Thread.sleep(1000);
表示当前线程暂停1000毫秒 ,其他线程不受影响,该语句会抛出InterruptedException中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
int seconds = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已经过去了" + ++seconds + "秒");
}
}
}
另外还有一种暂停的方法
TimeUnit.SECONDS.sleep(4);
join
所有进程,至少会有一个线程即主线程,即main方法开始执行,就会有一个看不见的主线程存在。
join可以理解为插队,如果没有t.join();
那么System.out.println("结束");
这一行语句可能不会在t线程运行结束再运行。
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new MyRunnable(), "A");
t.start();
t.join();
System.out.println(Thread.currentThread().getName() + "运行结束");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
int seconds = 0;
while (seconds < 3) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
}
}
}
运行结果:
A已经过去了1秒
A已经过去了2秒
A已经过去了3秒
main运行结束
setPriority
线程优先级从1-10,最小的为1,最大的为10,默认为5。可以通过getPriority();
public class Test {
public static void main(String[] args){
Thread t1 = new Thread(() -> {
int seconds = 0;
while (seconds < 5) {
System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
}
}, "A");
Thread t2 = new Thread(() -> {
int seconds = 0;
while (seconds < 5) {
System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
}
}, "B");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
yield
临时暂停
public class Test {
public static void main(String[] args){
Thread t1 = new Thread(() -> {
int seconds = 0;
while (seconds < 5) {
System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
}
}, "A");
Thread t2 = new Thread(() -> {
int seconds = 0;
while (seconds < 5) {
Thread.yield();
System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
}
}, "B");
t1.setPriority(Thread.NORM_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t1.start();
t2.start();
}
}
setDaemon
守护线程是为其他线程服务的线程。
在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
守护线程通常会被用来做日志,性能统计等工作。
Thread t = new MyThread();
t.setDaemon(true);
t.start();
线程同步
多个线程同时读写共享变量的时候,会出现数据不一致的问题。
如下述代码所示,两个线程同时操作一个资源时,一个自增,一个自减,循环1000次,按道理来说应该是0,实际上最后的结果不一定为0。
public class Test
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
++Count.num;
}
}, "A");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
++Count.num;
}
}, "A");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("over,num = " + Count.num);
}
}
class Count {
public static int num = 0;
}
这是因为++Count.num操作是不具有原子性的,所谓原子性操作是指不可中断的操作,比如赋值操作,原子操作是线程安全的。
int n = 1;
而++Count.num这个操作,实际上由三个原子操作组成
- 取num的值
- num + 1
- 把新值赋予 num
举例:
假如t1线程取num的值,还没有进行第二步操作,t2线程也取了num的值,此时线程就是不安全的。
如下图所示,两个线程一个自增一个自减,最终结果为1.
Java程序可以使用synchronized
关键字对一个对象进行加锁。
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (Count.num) {
for (int i = 0; i < 100; i++) {
++Count.num;
}
}
}, "A");
Thread t2 = new Thread(() -> {
synchronized (Count.num) {
for (int i = 0; i < 100; i++) {
--Count.num;
}
}
}, "B");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("over,num = " + Count.num);
}
}
class Count {
public static Integer num = 0;
}
同步方法
因为synchronized
锁住的是一个对象,让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。建议把synchronized
逻辑封装起来。
public class Test {
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
count.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
count.dec();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.get());
}
}
class Count {
public static Integer num = 0;
public void add() {
synchronized (this) {
++this.num;
}
}
public void dec() {
synchronized (this) {
--this.num;
}
}
public int get() {
return this.num;
}
}
上述代码中,synchronized
锁住的对象是this
,即是当前实例,下面两种写法是等价的。
public void add() {
synchronized(this) {
++this.num;
}
}
public synchronized void add() {
++this.num;
}
如果对一个静态方法使用synchronized
修饰时,对于静态方法,是没有this
实例的,因为静态方法是针对类而不是实例,所以对静态方法添加synchronized
,锁住的是该类的Class
实例,即下面两者是相等的。
public synchronized static void add() {
// ...
}
public static void add() {
synchronized (Count.class) {
// ...
}
}
死锁
在死锁之前,首先要知道可重入锁的概念。
Java的线程锁是可重入的锁。可重入锁意味着能被同一个线程反复获取的锁,就叫做可重入锁,如下代码所示
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (this) {
System.out.println("第1次获取锁,这个锁是:" + this);
int index = 1;
while (true) {
synchronized (this) {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
}
if (index == 5) {
break;
}
}
}
}
}).start();
}
}
上述代码如果使用lambda表达式创建,会出现语法报错,因为lambda表达式中的this指向的是所在外部类,而匿名内部类中this指的是匿名内部类当前对象。
new Thread(()->{
synchronized (this){ // 此处报错
// do something
}
}).start();
一个线程可以获取一个锁后,再继续获取另一个锁,如下代码可能发生死锁。
public class Test {
public static void main(String[] args) {
Zone z1 = new Zone("z1");
Zone z2 = new Zone("z2");
new Thread(()->{
synchronized (z1) {
System.out.println("占据z1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试占据z2");
synchronized (z2) {
System.out.println("成功占据z2");
}
}
}).start();
new Thread(()->{
synchronized (z2) {
System.out.println("占据z2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试占据z1");
synchronized (z1) {
System.out.println("成功占据z1");
}
}
}).start();
}
}
class Zone {
String name;
public Zone(String name) {
this.name = name;
}
}
如何解决,使两个线程获取的锁的顺序一致即可。
public class Test {
public static void main(String[] args) {
Zone z1 = new Zone("z1");
Zone z2 = new Zone("z2");
new Thread(()->{
synchronized (z1) {
System.out.println("占据z1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试占据z2");
synchronized (z2) {
System.out.println("成功占据z2");
}
}
}).start();
new Thread(()->{
synchronized (z1) {
System.out.println("占据z1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试占据z2");
synchronized (z2) {
System.out.println("成功占据z2");
}
}
}).start();
}
}
class Zone {
String name;
public Zone(String name) {
this.name = name;
}
}
线程交互(wait和notify)
百度百科:生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
java中使用wait和notify来解决线程交互的问题
public class Test {
public static void main(String[] args) {
TaskQueue taskQueue = new TaskQueue();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(10);
taskQueue.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100);
taskQueue.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者").start();
}
}
class TaskQueue {
private LinkedList<Integer> list = new LinkedList<>();
private static int MAX_SIZE = 10;
public synchronized void put() throws InterruptedException {
// 判断
while (list.size() == MAX_SIZE) {
System.out.println("容量已满~~");
this.wait();
}
// 添加
int item = (int) (Math.random() * 10);
list.add(item);
System.out.println(Thread.currentThread().getName() + "添加了" + item);
// 通知
this.notifyAll();
}
public synchronized void get() throws InterruptedException {
// 判断
while (list.isEmpty()) {
this.wait();
}
// 消费
Integer remove = list.remove();
System.out.println(Thread.currentThread().getName() + "消费了" + remove);
// 通知
this.notifyAll();
}
}
参考
正则表达式
final
http://www.51gjie.com/java/759.html
https://blog.youkuaiyun.com/sinat_32336967/article/details/94761771