java中的多线程
1,多任务
什么是多任务?
就比如人边吃饭边看手机,看似同时做了2件事情,但是本质上大脑在同一时间只做了一件事情。
2,多线程
## 什么是多线程?
就像车道,以前是2条道路,现在是4条道路。
一个进程(Process)可以有多个线程(Thread),比如视频中,同时有图像,声音等等。
3,进程与线程
程序是是一串指令和数据的有序集合,是一个静态的概念
进程是程序的一次执行过程,他是一个动态的概念,是系统分配资源的单位
一个进程可以有多个线程,但是一个进程中至少要有一个线程,线程是CPU执行和调度的单位
4,线程创建
4.1,发现问题
我们发现了问题,当多个线程操作同一个资源的时候,我们发现了问题, 线程不安全了,出现了数据紊乱的情况
4.2,callable的优势
可以返回值并且定义返回值的类型
可以抛出异常
4.3,函数式接口
什么是函数式接口?
如果一个接口中,只有一个抽象方法,那就称为函数式接口
对于函数式接口,我们就可以使用拉姆达表达式来创建该接口的对象
5,Thread 中的其他方法
yield方法,可以让行,从可用状态到了就绪状态
join方法,可以插队、让这一个方法运行完之后才去进行其他线程
6.线程状态
new
尚未启动的线程处于这个状态
runnable
在java虚拟机中执行的线程处于这个状态
blocked
被阻塞等待监视器锁定的线程处于这个状态
waiting
正在等待另一个线程执行特殊动作的线程处于这个状态
timed_waiting
等待另一个线程执行动作达到指定等待时间的线程处于这个状态
terminated
已经推退出的线程处于这个状态
注意:
一个线程不能启动两次,在第一次启动并结束之后,我们不能再重新启动
7,线程优先级
java提供了一个线程调度程序来监控程序中启动后进入就绪状态的所有线程,会按照优先级去决定先调用哪一个线程来运行,数字越大,优先度越高
注意:
优先级高不代表就一定先调度,而是指获得CPU调度的可能性高
8,守护(daemon)线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如后台记录操作日志,监控内存,垃圾回收等。。。
9,线程同步
多个线程操作一个资源,这就回归到我们最上面所遇到的问题
9.1,并发
同一个对象被多个线程同时操作
9.2,本质
线程同步本质上是一种等待机制,多个需要访问这个对线的线程会先进入这个对象的等待池并形成队列,当前一个线程使用完毕后,下一个线程再继续使用
9.3,队列和锁
在并发与线程同步中,光有队列是不够的,还需要锁吗,这两个东西才能保证线程同步的安全性
每个对象都有一个锁,sleep不会释放锁
队列
锁
锁机制(synchronized)
锁机制的问题:
一个线程持有锁会导致其他需要此锁的线程挂起
在多线程竞争下,加锁,释放锁,会导致比较多的上下文切换和调度延时,引发性能问题
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,也会引起性问题
9.4,同步方法
我们如果对变量进行private修饰,那么就可以保证这个变量只能被这个方法访问和修改,所以我们对方法进行处理并提出一套机制,这就是synchronized关键字
这个机制控制了对“对象”的访问,每个对象都有唯一的一把锁,如果有一个线程调用到了这个被synchronized修饰的方法,那就会独占了锁,直到这个被修饰的方法返回后,才会释放锁,使下一个线程来使用这个方法,从而保证了线程同步的安全
注意:
这个锁的机制会使得效率变低,所以在进行只读时,我们可以不使用这个方法;在进行修改操作时,为了保证安全去使用这个关键字去修饰方法
9.5,同步块
synchronized(OBJ){}
Obj称之为同步监视器
Obj可以是任何对象,但是我们推荐使用共享资源作为同步监视器
同步方法中不需要指定Obj,因为this,也就是这个对象本身,就是同步监视器
同步监视器的执行过程
第一个线程访问,锁定同步监视器
第二个线程访问,发现同步监视器被锁住,阻塞
第一个线程访问完毕,释放了同步监视器
第二个线程访问,发现没有上锁,可以锁定并使用
10,死锁
两个或多个线程都在等对方释放资源,都停止的情况,或者某一个同步块同时拥有“两个以上对象的锁”时,就有可能发生死锁问题
总结来说,就是2个线程,都在相互等待对方的资源
解决方法:
我们只要注意不要同时去需求一个锁就好,当出现死锁情况时,我们可以去试着把一把锁分成两把,这样就不会同时去请求一个锁的情况
11,Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制---通过显式定义同步锁对象来实现同步。同步锁使用Lock对象来充当
Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问之前应该先获得Lock对象
ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronize相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁
注意:
注意不要放在循环体之后,循环体之后的代码都是不生效的
12,线程协作
(消费者生产者模式)---这是一个问题,并不是一种模式
这就是要实现两个线程之间是如何进行通信的
场景:
假设仓库中只能存放一件商品,生产者将生产出来的产品放入仓库,消费者将从仓库中取走产品并消费
如果仓库中没有商品,则生产者把商品放入仓库,否者停止生产并等待,直到商品被取走
如果仓库中放有产品,则生产者取出商品并消费,否则停止消费并等待,直到仓库中被放入商品
这就涉及到了数据缓存区
这个本质上还是一个线程同步的问题,使用之前的方法是不行的
有两种方法可以解决这个问题:
管程法
通过逻辑来使用wait和notifyAll来等待或者换型线程
信号灯法
使用了一个表示位,来判断是否唤醒或者是否等待
13,线程池
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池子中
可以避免频繁的创建销毁,实现重复利用。类似生活中的公共交通工具
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中的线程,不需要每一次都创建)
便于线程管理
13.1,使用线程池
JDK5提供了线程池的API:ExecutorService和Executors
ExecutorService:真正的线程池接口
‘Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池