多线程与反射

A 多线程

一 线程

1.1 程序、进程、线程

  • 线程:独立执行的路径
  • 线程会带来额外的开销,如CPU调度的时间,并发控制开销
  • 线程开启了没有马上执行,由CPU进行调度

  • 注:很多的多线程是模的,真正的多线程用的是多个CPU,很多都是切换的比较快看起来像多线程

1.2 线程创建

  • 继承Thread类

    1. 自定义线程类继承Thread

    2. 重写run方法

    3. 调用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接口

    1. 定义类实现Runnable接口

    2. 重写run方法

    3. 执行线程需要丢入的runnable接口实现类,调用start方法

      龟兔赛跑:Thread_Study/code/Race.java

  • 实现Callable接口

    1. 实现Callable接口,需要返回值类型
    2. 重新call方法
    3. 创建目标对象
    4. 创建执行服务
    5. 提交执行
    6. 获取结果
    7. 关闭服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class TestCallable implements Callable {
    @Override
    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(Obj){} : 锁住的对象是变化的量
      • Obj称为同步监视器
        • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
        • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射)
      • 同步监视过程
        1. 第一个线程访问,锁同步监视器,执行其中代码
        2. 第二个线程访问,发现同步监视器被锁定,无法访问
        3. 第一个线程访问完毕,解锁同步监视器
        4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
  • 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 ExecutorServiceExecutors
    • ExecutorService:真正的线程池,常见子类ThreadPoolExecutor
      • viod execute(Runnable command):执行任务,无返回值
      • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,用来执行Callable
      • void shutdown():关闭连接池
    • Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池

B 反射

一 反射

1.1 反射

  • 反射机制:将类的各个部分封装为其他对象

  • image-20220918144836712

  • 优点

    • 在程序的运行过程中操作这些对象

    • 可以解耦,提高程序的可扩展性

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);