多线程与反射
多线程与反射
A 多线程
一 线程
1.1 程序、进程、线程
- 线程:独立执行的路径
- 线程会带来额外的开销,如CPU调度的时间,并发控制开销
线程开启了没有马上执行,由CPU进行调度
注:很多的多线程是模的,真正的多线程用的是多个CPU,很多都是切换的比较快看起来像多线程
1.2 线程创建
继承Thread类
自定义线程类继承Thread
重写run方法
调用start方法
1
2
3
4
5
6
7
8
9
10
11public class TestThread extends Thread{
public void run(){
sout("new Thread");
}
psvm(){
TestThread test = new TestThread();
test.start();
}
}
实现Runnable接口
定义类实现Runnable接口
重写run方法
执行线程需要丢入的runnable接口实现类,调用start方法
龟兔赛跑:Thread_Study/code/Race.java
实现Callable接口
- 实现Callable接口,需要返回值类型
- 重新call方法
- 创建目标对象
- 创建执行服务
- 提交执行
- 获取结果
- 关闭服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class TestCallable implements Callable {
public Boolean call() throws Exception {
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testCallable = new TestCallable();
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(testCallable);
//获取结果
boolean rs1 = r1.get();
//关闭服务
ser.shutdown();
}
}
二 其他知识
- 静态代理模式
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
- 好处:代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情
- Lamda表达式
- 前提是只有一行代码的情况下才能简化为一行,如果不是则不能去掉括号
- 前提是接口为函数式接口
- 多个参数也可以去掉参数类型,要去就都去掉
三 线程状态与方法
3.1 线程状态
- 新生
- 就绪
- 阻塞
- 运行
- 死亡
3.2 线程方法
setPriority(int newPriority)
static void sleep(long millis)
void join():线程终止
static void yield():暂停当前正在执行的线程,并执行其他线程
void interrupt():中断线程,别用这个方式
boolean isAlive():测试线程是否处于活动状态
3.3 线程停止
- 不建议使用stop方法停止线程,让线程正常停止(利用次数、使用标志位)
3.4 线程休眠
sleep()
指定的是毫秒
- 存在异常
InterruptedException
- sleep时间到达之后进入就绪状态
- 可以模拟网络延时
- 每个对象都有一个锁,sleep不会释放锁
3.5 线程礼让
yield()
定义:让当前正在执行的线程停止,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU心情
3.6 线程强制执行
join()
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞(插队)
3.7 观察线程状态
Thread.getState()
值:NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED
(Thread.State类型)
3.8 线程的优先级
- 用数字表示1-10,越大越高,大多数的时候是优先级高的先跑
getPriority() setPriority(int i)
- 先设置优先级再
start()
3.9 守护线程
threadName.setDaemon(true)
默认为false
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志、监控内存、垃圾回收等待
四 线程同步
多线程操作同一个资源(并发)
线程同步其实就是一种等待机制,多个需要同时访问同一个对象的线程进入对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用
形成条件:队列和锁
锁机制:synchronized
- 一个线程持有的锁会导致其他所有需要这个锁的线程挂起
- 多线程竞争下加锁、释放锁会导致比较多的上下文切换和调度延时,引擎性能问题
- 如果一个优先级高的线程等待一个优先级第的线程释放锁,会导致优先级倒置,引起性能问题
三大不安全案例
- Thread_Study/code/D1_UnsafeBuyTicket.java:买票
- Thread_Study/code/D2_UnsafeBank.java:取钱
- Thread_Study/code/D3_UnsafeList.java:list
同步方法及同步块
- synchronized关键字
- synchronized方法
pubic synchronized void method(int args){}
- synchronized块
- synchronized方法
- synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
- 缺陷:若一个大的方法申明为synchronized将会影响效率
- synchronized同步块
synchronized(Obj){}
: 锁住的对象是变化的量- Obj称为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射)
- 同步监视过程
- 第一个线程访问,锁同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
- synchronized关键字
CopyOnWriteArrayList
(JUC并发包中)
五 锁与线程通信
5.1 死锁
- 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
- 避免其中一个即可避免死锁的产生
- lock锁
- JDK5.0
ReentrantLock
类(可重入锁)实现了Lock接口- Lock是显式锁(手动开启和关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的拓展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(方法体之外)
5.2 线程协作(生duiy产者消费者模式)
- 对于生产者,没有生产产品之前,要通知消费者等待,生产之后又要通知消费者消费
- 对于消费者,消费后要通知生产者已经消费结束,需要生产新的产品
- 在这个模式中,仅有synchronized是不够的
- synchronized可阻止并发更新一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
- 几个方法
wait()
wait(long timeout)
notify()
notifyAll()
- 解决方式1
- 并发协作模型 “生产者/消费者模式” ——> 管程法
- 生产者负责生产数据
- 消费者负责处理数据
- 缓冲区:消费者不能直接使用生产者的数据
- 并发协作模型 “生产者/消费者模式” ——> 管程法
- 解决方式2
- 并发协作模型 “生产者/消费者模式” ——> 信号灯法
5.3 线程池
- 避免了频繁的创建和销毁线程
- 便于管理、提高响应速度
- JDK5.0
ExecutorService
和Executors
ExecutorService
:真正的线程池,常见子类ThreadPoolExecutorviod execute(Runnable command):执行任务,无返回值
<T> Future<T> submit(Callable<T> task):执行任务,有返回值,用来执行Callable
void shutdown():关闭连接池
Executors
:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
B 反射
一 反射
1.1 反射
反射机制:将类的各个部分封装为其他对象
优点
在程序的运行过程中操作这些对象
可以解耦,提高程序的可扩展性
1.2 获取字节码class类的三个方式
Class.forName(“全类名”)
:将字节码文件加载进内存- 多用于配置文件,将类名定义在配置对象中,读取文件加载类
类名.class
:通过类名的属性class获得- 多用于参数的传递
对象.getClass()
:object类中定义- 多用于对象的获取字节码方式
注:同一个class文件(字节码文件)在一次程序运行过程中只加载一次,不论通过哪一种方式获取的class对象都是同一个。
1.3 使用class对象
获取功能
获取成员变量
Field[] getFields()
- 获取public修饰的
Field getField(String name)
Field[] getDeclaredFields()
不考虑修饰符
若访问了非public,则需要在访问前设置忽略安全访问
fieldName.setAccessible(true);//暴力反射
Field getDeClaredFields(String name)
获取构造方法
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
获取成员方法
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
获取类名
String getName()
Field:成员变量
- get set
- 忽略访问权限修饰符的安全检查:
setAccessible(true);//暴力反射
Constructor:构造方法
- 创建对象:
ConstructorName.newInstance(Object... initargs)
- 若使用空参构造方法创建对象,则可以简化操作:
Class对象的newInstance
- 创建对象:
Method
- 执行方法:
methodName.invoke(Object obj, Object... args)
- 执行方法:
1.4 反射案例
写一个框架,不该变该类的任何代码实现创建任意类,执行任意方法(改变配置文件)
- 实现:
- 配置文件
- 反射
- 步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
- 实现:
配置文件pro.properties文件
1
2className = cn.itcast.domain.Person
methodName = eatReflectTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//1 加载配置文件
//1.1 创建properties对象
Properties pro = new Properties();
//1.2 加载配置文件,转换为一个集合
//1.2.1 获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResoureAsStream("pro.properties")
pro.load(is);
//2 获取配置文件中定义的数据
String className = pro.getProperties("className");
String methodName = pro.getProperties("methodName");
//3 加载类进内存
Class cls = class.forName(className);
//4 创建对象
Object obj = cls.newInstance();
//5 获取方法对象
Method method = cls.getMethod(methodName);
method.invoke(obj);