学习目标:
小白起飞第23天
学习内容:
多线程
守护线程概述
守护线程 又叫兜底线程
每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
但是必须在启动 static之前,否则报错
Timer 概述
定时器 计划任务,只要有一个任务监听 就会是一个线程
1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间
死锁
锁相关知识
如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
但是和静态无关,和其他对象也无关
如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
但是和成员无关
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
原理 :
1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
代码块锁
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定
如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
线程通信 概述
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 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
2 业务类需要有对应的生产方法和消费方法
1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
3 两个线程分别调用生产和消费
单例模式 概述
单例模式目的 : 让一个类只创建一个对象
根据对象的创建时机不同,分为两种
懒汉模式 : 第一次使用的时候创建对象
饿汉模式 : 类加载的时候创建对象
实现步骤 :
1 构造方法私有化
2 公共的静态方法用于获取对象
3 私有化静态变量存储创建后的对象
问题-多线程环境下不行
分析原因
因为多线程环境存在并发性和并行性 , 有可能同时执行到这个方法,导致创建多个对象
解决方案1
加锁 可以解决,一定不会出现问题
但是会有新的问题,
没有加锁前,出现问题,是因为,第一次执行的时候,多个线程并行执行到这个判断了
因为第一次执行,s是null,没有对象,所以导致创建多个对象
但是 一旦跳过第一次,后续不管多少个并发/并行 s都不再等于null,就不会再创建
而我们如果使用synchronized修饰方法的话,虽然结果一定没有问题,但是效率降低太多了
因为不仅仅是第一次,任何时候只要想来获取对象,都需要排队等待
而 我们这个程序中,其实只需要保证第一次排队等待即可,一旦创建了对象之后,则不需要排队,即使在这时候有很多并行线程同时执行,判断s==null的时候 也是false,因为有对象了
解决方案2
通过上面程序,我们不能再方法加锁了,因为影响效率
这样的话,只有第一次请求需要排队,一会就算有很多线程同时执行这个方法,也不会排队等待,因为方法没有加锁,多个线程 可以同时进来执行该方法
另外s已经在第一次请求的时候赋值过了,所以判断s==null时 也是false
所以 这种写法才是多线程环境下的最佳选择
线程池
概述
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
a)提高响应速度(减少了创建新线程的时间)
b)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c)便于线程管理
i.corePoolSize:核心池的大小
ii.maximumPoolSize:最大线程数
iii.keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池的作用 :
线程池作用就是限制系统中执行线程的数量
根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
少了浪费系统资源,多了造成系统拥挤效率不高
用线程池控制线程数量,其他线程排队等候
一个任务 执行完成,再从队列中取最前面的任务开始执行
如果队列中没有等待进程,线程池的这个资源处于等待状态
当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
否则需要进入等待队列
为什么要使用线程池
1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
使用方式
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):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
NewCachedThreadPool
创建一个可根据需要创建新线程的线程池
创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
NewFixedThreadPool
创建一个固定 长度线程池,可控制线程最大并发数
超出此数量的线程,会在队列中等待
NewScheduledThreadPool
创建一个固定长度线程池,支持定时及周期性执行任务
NewSingleThreadExecutor
单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他
该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行
适用于一个一个任务执行的情况