多线程与反射
多线程与反射
A 多线程
一 线程
1.1 程序、进程、线程
- 线程:独立执行的路径
- 线程会带来额外的开销,如CPU调度的时间,并发控制开销
- 线程开启了没有马上执行,由CPU进行调度 
- 注:很多的多线程是模的,真正的多线程用的是多个CPU,很多都是切换的比较快看起来像多线程 
1.2 线程创建
- 继承Thread类 - 自定义线程类继承Thread 
- 重写run方法 
- 调用start方法 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- public 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
 18- public 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:真正的线程池,常见子类ThreadPoolExecutor- viod 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 
 2- className = cn.itcast.domain.Person 
 methodName = eat
- ReflectTest.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);


