【Java多线程】总结(三)死锁 sleep方法和wait方法的区别 线程通讯 线程休眠 线程池创建 线程池拒绝策略

1 死锁

1 死锁概念:两个线程在拥有锁的情况下,又在尝试获取对方锁

2 死锁产生的必要因素:

  1. 互斥条件:一个资源同一时间只能被一个线程占用,当这个资源被占用后其他线程只能等待

  2. 不可剥夺条件:一个资源被占有后,如果不是拥有资源的线程主动释放,其他线程不可用由此资源

  3. 请求并持有条件:当一个线程拥有一个资源是不满足,并且申请其他资源

  4. 环路等待条件:多个线程在请求资源的情况下,形成了环路链

    以上四个条件是导致死锁的必要条件,四个条件缺一不可

3 解决死锁:改变产生死锁原因中的任意⼀个或多个条件就可以解决死锁的问题,其中可以被修改的条件只有两个:请求并持有条件 和 环路等待条件。

  1. 请求并持有条件(可人为控制

    1 不去拥有不可抢占资源

public class ThreadDead {
    public static void main(String[] args) {
        Object lockA=new Object();
        Object lockB=new Object();
        Thread t1=new Thread(()->{
            synchronized (lockA){
                System.out.println("线程1获取到锁A");
                //空档期 让对方先得到锁B
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                synchronized (lockB){
//                    System.out.println("线程1得到锁B");
//                    System.out.println("线程1释放锁B");
//                }
                System.out.println("线程1释放锁A");
            }
        });
        t1.start();
        Thread t2=new Thread(()->{
            synchronized (lockB){
                System.out.println("线程2获取到锁B");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                synchronized (lockA){
//                      System.out.println("线程2得到锁A");
//                      System.out.println("线程2释放锁A");
//                }
                System.out.println("线程2释放锁B");
            }
        });
        t2.start();
    }
}
线程1得到锁A
线程2得到锁B
线程2得到锁B
线程1得到锁A

  1. 破坏环路等待条件(可人为控制

    按照已经编好的序号顺序执行

public class UnDeadLock2 {
    public static void main(String[] args) {
        Object lockA = new Object();
        Object lockB = new Object();
 
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程1得到锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB) {
                    System.out.println("线程1得到锁B");
                    System.out.println("线程1释放锁B");
                }
                System.out.println("线程1释放锁A");
            }
        }, "线程1");
        t1.start();
 
        Thread t2 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程2得到锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB) {
                    System.out.println("线程2得到锁B");
                    System.out.println("线程2释放锁B");
                }
                System.out.println("线程2释放锁A");
            }
        }, "线程2");
        t2.start();
    }
}
线程1得到锁A
线程1得到锁B
线程1得到锁B
线程1得到锁A
线程2得到锁A
线程2得到锁B
线程2得到锁B
线程2得到锁A

2. 线程通讯

1 主要方法:(对象级别 都是obj内置方法)

  1. wait()/wait(long timeout):线程进入休眠状态

  2. notify() : 随机唤醒休眠线程

  3. notifyAll() :唤醒所有处于休眠状态的线程

注意这三个⽅法都需要配合 synchronized ⼀起使⽤

2 wait使用

synchronized (lock){// 2.synchronized wait 必须配合使用
    System.out.println("线程1开始执行wait方法");
    lock.wait();// 1.lock对象必须是同一个
}
  1. wait 执⾏流程

    1 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中) eg:xxx.wait()
    2 释放当前的锁
    3 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁

  2. wait 结束等待的条件:

    1 其他线程调⽤该对象的 notify ⽅法.
    2 wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).eg:wait(XX)
    3 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常

3 notify使用:

  1. notify调用后并不是立马执行唤醒线程操作,而是等notify中的synchronized执行完(释放完锁),才能去唤醒操作;

4 notifyAll使用:同一把锁,随机唤醒所有休眠线程,其他同上

3 线程通讯注意事项:

1

  1. wait /notify /notifyAll 必须要配合synchronized一起使用(保证执行顺序,防止混乱(比如wait notify顺序颠倒就会执行异常))

  2. wait /notify /notifyAll 进行synchronized加锁,一定要使用同一个对象加锁

  3. 当调用wait /notify /notifyAll之后,程序不会立马执行唤醒操作,而是尝试获取锁,只有得到锁之后才能继续执行

  4. notifyAll 并不是唤醒所有的wait等待的线程 而是唤醒当前对象处于wait等待的线程

2 wait() VS wait(long timeout):

  1. 不同点:

    1 wait(long timeout):当线程执行到达设置的时间后自动恢复执行
    wait() 无限等待状态

    2 调用wait(long timeout):TIMED_WAITING ;调用wait():WAITING;

  2. 共同点

    1 无论是有参还是无参的等待方法,都可以使得当前线程进入休眠状态;

    2 无论是有参还是无参的等待方法,都可以使用notify/notifyAll进行唤醒

3 wait(0)VS sleep(0)

  1. wait(0):无限期等待
  2. sleep(0):相对于Thread.yeild(),让出cpu执行权,重新调度,sleep(0)会继续执行

4 wait sleep 在有有锁情况下释放锁的行为

  1. wait方法不管是有参的还是无参的在执行的时候都会释放锁,而sleep方法不会释放锁

5 wait VS sleep

  1. 相同点:

    1 都可以让线程进入休眠状态

    2 都可以响应interrupte(中断)请求

  2. 不同点:

    1 wait 必须在 synchronized 中使⽤,⽽ sleep 却不⽤;
    2 sleep 是 Thread 的⽅法,⽽ wait 是 Object 的⽅法;
    3 sleep 不释放锁,wait 释放锁;
    4 sleep 有明确的终⽌等待时间,⽽ wait 有可能⽆限期的等待下去;
    5 sleep 和 wait 产⽣的线程状态是不同的,sleep 是 TIMED_WAITING 状态,⽽ wait 是 WAITING 状态
    6 一般情况下 sleep只能等待超时时间后执行 而wait可以接受notify/notifyAll的唤醒

4 线程休眠和指定唤醒:LockSupport

1 小tips:线程休眠其他方法:

  1. sleep
  2. TimeUnit
  3. wait/notify/notifyAll
  4. LockSupport. park/umpark(thread)

2 主要方法(LockSupport ⽆需配和 synchronized 使⽤ )

  1. LockSupport.park():休眠当前线程。

  2. LockSupport.unpark(线程对象):唤醒某⼀个指定的线程

  3. LockSupport.parkUntil(long) :等待最⼤时间是⼀个固定时间

    LockSupport.parkUntil(System.currentTimeMillis()+3000); 休眠3s后执行自动恢复执行

5 线程池:

传统线程的缺点:

  1. 每次都要创建和消耗线程,需要消耗系统资源。
  2. 线程没有任务管理的功能,当任务量较大的时候没有任务队列对任务进行管理或拒绝任务。

1 线程池基本概念:

  1. 定义:线程池(ThreadPool)是⼀种基于池化思想管理和使⽤线程的机制。

  2. 池化思想在计算机的应⽤也⽐较⼴泛:
    内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎⽚。
    连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
    实例池(Object Pooling):循环使⽤对象,减少资源在初始化和释放时的昂贵损耗。

  3. 优点:

    1 降低资源消耗 :复用线程,降低线程创建和销毁造成的损耗。

    2 提⾼响应速度:任务到达时,⽆需等待线程创建即可⽴即执⾏。

    3 提高线程可管理性 :控制线程的数量,防止因为无限制创建线程导致的资源损耗问题,提高系统稳定性,使线程可以进程统一分配,调优和监控,可以实现任务缓存和任务拒绝等情况。

    4 线程池提供了更多功能:线程池具备可拓展性 ,⽐如延时定时线程ScheduledThreadPoolExecutor,就允许任务延期执⾏或定期执⾏

2 线程池的使用:

  1. 两种实现方式:

    1 通过ThreadPoolExecutor 手动创建线程池(1种实现方法
    2 通过Executors手动创建线程池(6种实现方法

  2. 七种实现方法:

    1 Executors.newFixedThreadPool:创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

    1. submit(new Runnable() 可以执行有返回值或没返回值的任务
    2. execute(new Runnable() 只能执行没返回值的任务

    2 Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;

    1. 根据任务数创建线程池 并在一定时间内可以重复使用线程适用于短时间内大量任务的线程 缺点是可能会占用用很多资源

    3 Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

    1. 单线程的线程池有什么意义
      1 复用线程

      2 提供任务队列(忙时放)和拒绝策略(满时拒绝)

    4 Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;

    1. .scheduleAtFixedRate()

      //2秒后开始执行定时任务 定时任务每隔4秒后执行一次 固定时间
      // (以上一次任务的开始时间作为延时任务的间隔的开始时间)
      // 延时和任务执行时间哪个大就以哪个时间作为间隔时间
      service.scheduleAtFixedRate(new Runnable() {
          @Override
          public void run() {
              System.out.println("任务执行:"+LocalDateTime.now());
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      },2,4,TimeUnit.SECONDS);
      
      
    2. .scheduleWithFixedDelay()

      //2秒之后开始执行 任务完成后 再过4秒开始执行任务
      //(以上一次任务的结束时间作为延时任务的间隔的开始时间)
      service.scheduleWithFixedDelay(new Runnable() {
          @Override
          public void run() {
              System.out.println("执行时间:"+LocalDateTime.now());
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      },2,4,TimeUnit.SECONDS);
      

    5 Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;

    6 Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】

    1. 根据当前系统配置生成线程数

    7 ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置

    ThreadPoolExecutor executor=new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), factory, new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("我执行了自定义拒绝策略");
        }
    });
    1.核心线程数
    2.最大线程数
    3.存活时间(临时员工可闲置存活时间 )
    4.针对3的描述
    5.存放阻塞任务的队列
    6.线程工厂
    7.拒绝策略管理器(处理极端任务 任务队列满了的措施)
    

3 线程池重要执行节点:

在这里插入图片描述

  1. 当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于直接创建线程并执行任务

  2. 当实际线程数大于核心线程数,他会判断任务队列是否已满,未满直接将任务放队列即可

  3. 判断线程池的实际线程数是否大于最大线程数,如果小于最大线程数会直接创建线程执行任务;实际线程数已经等于最大线程数,会直接执行就拒绝策略

4 线程池拒绝策略:(4【jdk提供的拒绝策略】1【自定义】)

在这里插入图片描述

  1. 忽略最新的任务
  2. 提示异常,拒绝执行(默认)
  3. 使用调用线程池的线程来执行任务
  4. 忽略旧任务(任务队列第一个队伍)
  5. 自定义:new RejectedExecutionHandler(){};

5 线程池状态:

  1. running:线程池创建之后的状态,此状态下可执行任务

  2. shutdown:该状态下线程池不再接受新的任务,但是会将工作队列的任务执行完

  3. stop:该状态下线程池不再接受新的任务,会将工作队列的任务执行完,会中断线程

  4. tidyong:该状态下所有任务终止,将会执行terminated()钓子方法

  5. terminated:执行完terminated()调子方法后

    shutdown 执⾏时线程池终⽌接收新任务,并且会将任务队列中的任务处理完;
    shutdownNow 执⾏时线程池终⽌接收新任务,并且会给终⽌执⾏任务队列中的任务。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值