多线程

Day23-多线程

  1. 多线程
    1.1 守护线程
    1.1.1 概述
  • 守护线程 又叫兜底线程
  • 每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
  • 简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
  • 但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
  • 但是必须在启动 start之前,否则报错
    1.1.2 使用

在这里插入图片描述

1.2 Timer
1.2.1 概述

  • 定时器 计划任务,只要有一个任务监听 就会是一个线程
  • 1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间

1.2.2 使用
在这里插入图片描述

1.3 死锁
1.3.1 锁相关知识

  • 如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
  • 但是和静态无关,和其他对象也无关
  • 如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
  • 但是和成员无关

在这里插入图片描述

运行结果
在这里插入图片描述

M1执行 m3执行 五秒以后m2执行

在这里插入图片描述

在这里插入图片描述

1.3.2 概述

死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

  • 死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
  • 原理 :
  •  1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
    
  •  2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
    
  •  3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
    
  •  3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
    
  • 代码块锁
  •  synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
    
  •  如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
    
  •  同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定		
    
  •  如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
    
  •  同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
    

1.3.3 代码实现

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.4 线程通信
1.4.1 概述

在这里插入图片描述

wait() 与 notify() 和 notifyAll()
a)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c)notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声

  • Object 中的方法
  • wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行
  • notify() : 唤醒该对象上等待中的某一个线程(优先级)
  • notifyAll() : 唤醒该对象上所有等待的线程
  • 必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)
  • wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
  • 也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒
  • 注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
  •  	而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
    

1.4.2 使用方式
以打印奇数偶数为例

  •  1 有一个业务类 Num ,其中有一个成员变量 count
    
  •  2 业务类提供打印奇数和偶数的方法
    
  •  		1 奇数 printOdd : 打印奇数, count++ , 变成偶数,唤醒其他线程,自身进入wait等待
    
  •  		2 偶数 printEven : 打印偶数 count++ , 变成奇数,唤醒其他线程,自身进入wait等待
    
  •  3 两个线程,保存同一个Num对象,分别调用 printOdd和printEven
    

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4.3 面试题之生产者与消费者

  • 比较经典的面试题 : 生产者和消费者
  • 思路 :
  •  1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
    
  •  2 业务类需要有对应的生产方法和消费方法
    
  •  		1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
    
  •  		2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
    
  •  		3 两个线程分别调用生产和消费
    
  • wait 和 notify

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5 单例模式
1.5.1 概述

  • 单例模式目的 : 让一个类只创建一个对象
  • 根据对象的创建时机不同,分为两种
  •  	懒汉模式 : 第一次使用的时候创建对象
    
  •  	饿汉模式 : 类加载的时候创建对象
    
  • 实现步骤 :
  •  1 构造方法私有化
    
  •  2 公共的静态方法用于获取对象
    
  •  3 私有化静态变量存储创建后的对象
    

1.5.2 之前的编码

在这里插入图片描述

在这里插入图片描述

只创建一个对象执行 合格
1.5.3 问题-多线程环境下不行
1.5.3.1 分析原因
因为多线程环境存在并发性和并行性 , 有可能同时执行到这个方法,导致创建多个对象

在这里插入图片描述

通过结果测试,创建时不一定是一个对象,原因是因为这样…

在这里插入图片描述

1.5.3.2 解决方案1

在这里插入图片描述
在这里插入图片描述

加锁 可以解决,一定不会出现问题

但是会有新的问题,
在这里插入图片描述

没有加锁前,出现问题,是因为,第一次执行的时候,多个线程并行执行到这个判断了
因为第一次执行,s是null,没有对象,所以导致创建多个对象

但是 一旦跳过第一次,后续不管多少个并发/并行 s都不再等于null,就不会再创建

而我们如果使用synchronized修饰方法的话,虽然结果一定没有问题,但是效率降低太多了

因为不仅仅是第一次,任何时候只要想来获取对象,都需要排队等待

而 我们这个程序中,其实只需要保证第一次排队等待即可,一旦创建了对象之后,则不需要排队,即使在这时候有很多并行线程同时执行,判断s==null的时候 也是false,因为有对象了

所以 以上编码 , 不是最佳选择

1.5.3.3 解决方案2
通过上面程序,我们不能再方法加锁了,因为影响效率

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这样的话,只有第一次请求需要排队,一会就算有很多线程同时执行这个方法,也不会排队等待,因为方法没有加锁,多个线程 可以同时进来执行该方法
另外s已经在第一次请求的时候赋值过了,所以判断s==null时 也是false.这种写法才是多线程环境下的最佳选择

1.6 线程池

1.6.1 概述
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
a)提高响应速度(减少了创建新线程的时间)
b)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c)便于线程管理
i.corePoolSize:核心池的大小
ii.maximumPoolSize:最大线程数
iii.keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • 线程池的作用 :
  • 线程池作用就是限制系统中执行线程的数量
  • 根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
  • 少了浪费系统资源,多了造成系统拥挤效率不高
  • 用线程池控制线程数量,其他线程排队等候
  • 一个任务 执行完成,再从队列中取最前面的任务开始执行
  • 如果队列中没有等待进程,线程池的这个资源处于等待状态
  • 当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
  • 否则需要进入等待队列
  • 为什么要使用线程池
  •  1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
    
  •  2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
    
  •  		(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
    

1.6.2 使用方式

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
 Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

1.6.2.1 NewCachedThreadPool
创建一个可根据需要创建新线程的线程池
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
在这里插入图片描述

结果是一块出来的 说明是一起睡眠的 线程池中

1.6.2.2 NewFixedThreadPool

  • 创建一个固定 长度线程池,可控制线程最大并发数
  • 超出此数量的线程,会在队列中等待

在这里插入图片描述

1.6.2.3 NewScheduledThreadPool
创建一个固定长度线程池,支持定时及周期性执行任务

在这里插入图片描述
在这里插入图片描述

1.6.2.4 NewSingleThreadExecutor

  • 单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他
  • 该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行
  • 适用于一个一个任务执行的情况

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值