今日内容

  • 线程安全,死锁,线程状态,线程间通信,线程池

1.线程安全

1.1线程安全产生的原因

  • 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

    1.2线程的同步

  • 概述:java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证该变量的唯一性和准确性

  • 分类

    • 同步代码块
    • 同步方法
    • 锁机制,Lock

      1.3同步代码块

  • 概述:锁住多条语句操作共享数据,可以使用同步代码块实现

  • 格式:

synchronized(任意对象){
多条语句操作共享数据的代码
}

  • 注意:
    • 1.默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
    • 2.当线程执行完出来了,锁才会自动打开
  • 同步的好处和弊端:

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形之中会降低程序的运行效率

      1.4同步方法

  • 概述:把synchronized关键字加到方法上

  • 格式:修饰符 synchronized 返回值 方法名 (方法参数){ }
  • 同步代码块和同步方法的区别:

    • 1.同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
    • 2.同步代码块可以指定锁对象,同步方法不能指定锁对象
      • 注意:同步方法时不能指定锁对象的,但是有默认存在的锁对象的
    • 1.对于非static方法,同步锁就是this
    • 2.对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。Class类型的对象

      1.5Lock锁

  • 概述:为了更清晰的吧i澳大如何加锁和释放锁,JKDK5以后提供了一个新的锁对象Lock

  • Lock中提供了获得锁和释放锁的方法
    • void lock():获得锁
    • void unlock():释放锁
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
    • ReentrantLock的构造方法
    • ReentrantLock():创建一个ReentrantLock的实例
  • 注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unLock最后能够调用

    2线程死锁

    2.1概述:

  • 死锁是一种少见的,而且难于调试的错误,在两个线程对两个同步锁对象具有循环依赖时,就会大概率出现死锁。我们要避免死锁的产生,否则一旦死锁,出了重启没有其他办法

    2.2产生条件:

  • 多个线程

  • 存在锁对象的循环依赖

    3线程的状态

  • 新建状态 (NEW) —-> 创建线程对象

  • 就绪状态(RUNNABLE) —-> start 方法
  • 阻塞状态(BLOCKED) —-> 无法获得锁对象
  • 等待状态(WAITING) —-> wait方法
  • 计时等待(TIMED_WAITING) —-> sleep方法
  • 结束状态(TERMINATED) —-> 全部代码运行完毕
  • image-20210411221949088.png

    4线程通信

  • 线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务,例如经典的生产者和消费者案例。等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒,需要用到两种方法:

    • 等待方法:
      • void wait():让线程进入无限等待
      • void wait(long timeout):让线程进入计时等待
      • 以上两个方法调用会导致当前线程释放掉锁资源
    • 唤醒方法:
      • void notify():唤醒在此对象监视器(锁对象)上等待的单个线程
      • void notifyAll():唤醒在此对象监视器上等待的所有线程
      • 以上两个方法调用不会导致当前线程释放掉锁资源
  • 注意:

    • 等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中调用)
    • 等待和唤醒方法应该使用相同的锁对象调用

      5线程池

      5.1线程使用存在的问题

  • 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  • 如果大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源

    5.2线程池的介绍

  • 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省却了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

    5.3线程池使用的大致流程

  1. 创建线程池指定线程开启的数量
  2. 提交任务给线程池,线程池中的线程就会获取任务,进行处理任务
  3. 线程处理完任务,不会销毁,而是返回到线程池中,等待下一个任务执行
  4. 如果线程池中的所有线程都被占用,提交的任务,只能等待线程池中的线程处理完当前任务

    5.4线程池的好处

  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作都可以被重复利用,可执行多个任务
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建,就能立即执行
  • 提高线程的可管理性。可以根据系统的陈寿能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机

    5.5JAVA提供好的线程池

  • java.util.concurrent.ExectorService是线程池接口类型。使用时我们不需自己实现,JDK已经帮我们实现好了

  • 获取线程池我们使用工具类java.util.concurrent.Executors的静态方法
    • public static ExecutorService newFixedThreadPool(int num):指定线程池最大线程池数量获取线程池
  • 线程池ExecutorService的相关方法
    • Future submit(Callable task)
    • Future<?>submit(Runnable task)
  • 关闭线程池方法(一般不适用关闭方法,除非后期不用或者很长时间都不用,就可以关闭)

    • void shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务

      线程类的常用功能

      获取和设置线程名称

  • 获取线程的名字

    • String getName(); 返回此线程的名称
  • Thread类中设置线程的名字
    • void setName(String name):将此线程的名称更改为等于参数 name
    • 通过构造方法也可以设置线程名称
  • 获取当前线程的对象
    • public static Thread currentThread():返回对当前正在执行的线程对象的引用
  • 线程休眠
    • public static void sleep(long time):让线程修满指定的时间,单位为毫秒
    • public void join():具备阻塞作用,等待这个线程死亡,才会执行其他线程