1. 多线程:
(1) 多线程,同一时间,做多件事情
(2) 同一个时间点,只能做一件事情
(3) 同步与异步
2. Java中的多线程:
从main方法入口,启动Main线程,两种多线程实现方式
(1) 继承Thread类
(2) 实现Runnable接口,这个是推荐使用
原因:
(1) Java是单继承,可以实现多个接口
(2) Runnable本身是任务的概念
3. 进程与线程:
(1) 进程:CPU分配资源的最小单位
(2) 线程:本身不占用资源,但是要消耗进程分配的资源
(3) 一个应用进程有多个进程,一个进程有多个线程
4. 生命周期
(1) 状态:
a. 新建状态(New):创建一个线程对象
b. 就绪状态(Runnable):线程对象创建后,其他线程调用该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
c. 运行状态(Running):就绪状态的线程获取CPU,执行程序代码
d. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
e. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中(wait会释放持有的锁)
f. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中
g. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
h. 死亡状态(Dead):线程执行完了或者因异常退出run()方法,该线程结束生命周期
(2) 常用方法
a. 静态方法:sleep()\yield()
b. 普通方法:join()、wait()、notify()、notifyAll()、setPriority()、currentThread()
5. 线程同步和线程通信
(1) 线程同步:将操作共享数据的代码作为一个整体,同一时间只允许一个线程执行,执行过程中其他线程不能参与执行。
目的:防止多个线程访问一个数据对象时,对数据造成的破坏
① 同步方法:与有可能发生资源共享的方法添加synchronized关键字修饰
Public synchronized void run(){}
② 同步代码块:有可能发生资源共竞的代码块,给它加一个同步锁,即在代码块前添加synchronized来修饰
Synchronized (obj){}
③ 同步锁(Lock):Lock对象与资源对象同样具有一对一的关系
Class XX{
//显示定义Lock同步锁对象,此对象与共享资源具有一对一关系
Private final Lock lock=new ReentrantLock();
Public void mx{
//加锁
Lock.lock();
//需要进行线程安全同步的代码
//释放同步锁
Lock.unlock();
}
}
(2) 线程通信:
① Wait():导致当前线程等待并使其进入到等待阻塞状态。知道其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程
② Notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程.
③ notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程
这三个方法都是Object中的方法,必须与synchronized一起使用
Wait()与sleep()区别:
1) wait()是Object类的方法,sleep()是Thread类的静态方法
2) Wait()必须与synchronized一起使用,sleep()则不用
3) Wait()完,线程进入等待队列,而sleep()完,线程会进入阻塞队列
4) Wait()方法会释放同步锁,而sleep()则不会释放同步锁
5) 都会抛出InterruptedException
6. 生产者与消费者:也称为有限缓冲的问题
(1) 生产者:主要作用是生成一定量的数据放到缓冲区中,然后重复此过程
(2) 消费者:生产者生成好一定量到缓冲区的同时,消费者也在缓冲区消耗这些数据。
(3) 步骤:
① 因为生产者和消费者都是共享一块区域,即仓库
② 仓库是有容量上限的,当数量达到上限后,生产者不允许继续生产产品,当前线程进入等待状态,等待其他线程唤醒
③ 当仓库没有产品时,消费者不允许继续消费,当前线程进入等待状态,等待其他线程唤醒
(4) 代码实现:
① 生产者跟消费者之间消费的是产品,定义一个产品类
Public class Product{
Int id;//定义产品的唯一ID
//定义构造方法初始化产品id
Public Product(int id){
This.id=id;
}
}
② 定义一个仓库用来存放产品
Public class Repertory{
//定义一个集合类用于存放产品,规定仓库最大容量为10
Public LinkedList<Product> store=new LinkedList<Product>();
Public LinkedList<Product> getStore(){
Return store;
}
Public void setStore(LinkedList<Product> store){
This.store=store;
}
/*生产者
*push()用于存放产品
*参数:第一个是产品对象
*第二是线程名称,用来显示是谁生产的
*使用synchronized关键字修饰方法
*最多只能一个有一个线程同时访问方法
*/
Public synchronized void push(Product p,String threadName){
/*仓库容量最大值为10,当容量=10时进入等待状态,等待其他线程 *唤醒,唤醒后继续循环,等到仓库的存量小于10,跳出循环继续向 *下执行准备生产产品
*/
While(store.size()==10){
Try{
//打印日志
System.out.println(threadName+”报告:仓库已满->进入等待状态->呼叫消费者消费”);
//仓库容量已满,进入等待状态,等待被唤醒
This.wait();
} catch(Exception e){
}
}
This.notifyAll();//唤醒所有等待线程
Store.addLast(p);//将产品添加到仓库中
System.out.println(threadName+”生产’+p.id+”号产品”+” ”+”当前库存:”+store.size());
Try{
Thread.sleep(1000);
}catch(Exception e){
}
}
/*消费者方法
*pop()方法用于存放产品
*/
Public synchronized void pop(String threadName){
//仓库没有存货,消费者进入等待被唤醒,唤醒后进行循环
While(store.size()==0){
Try{
System.out.println(threadName+”仓库已空,进入等待,生产者生成产品”);
This.wait();
} catch(Exception e){}
}
This.notifyAll();//唤醒所有等待线程
System.out.println(threadName+”消费了:”+store.removeFirst().id+”号产品”+” ”+”当前库存:”
Try{
Thread.sleep();
}catch(Exception e){
}
}
}
③ 定义生产者
Public class Producer implements Runnable{
//定义一个静态变量记录产品号数,唯一
Public static Integer count=0;
Repertory repertory=null;//定义仓库
Public Producer(Repertory repertory){
This.repertory=repertory;
}
@Override
Public void run(){
While(true){
Synchronized(Producer.class){
Count++;
Product product=new Product(count);
Repertory.push(product,thread.currentThread().getName());
}
}
}
}
④ 定义一个消费者
Public class Consumer implements Runnable{
Repertory repertory=null;//定义仓库
Public Consumer(Repertory repertory){
This.repertory=repertory;
}
@Override
Public void run(){
While(true){
Repertory.pop(Thread.currentThread().getName());
}
}
}
7. 哲学者吃饭:用来表示在并行计算中多线程同步时产生的问题,就可以抽象成是资源抢占问题,而筷子就是“资源”。
哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的筷子,永远都在等右边的筷子。即使没有死锁,也可能发生资源耗尽。
死锁:是指两个或者两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,都将无法推进去。称系统处于死锁状态或系统产生死锁。
触发死锁的四个条件:
1) 互斥条件:线程对资源的访问是排他性,如果一个线程对占用了某资源,那么其他线程则必须处于等待的状态,知道资源被释放
2) 请求和保持条件
3) 不剥夺条件
4) 环路等待条件
而产生死锁的原因:
1) 因为系统资源不足
2) 进程运行推进的顺序不合适
3) 资源分配不当等
活锁:是指线程1可以使用资源,同时可以让其他线程先使用资源,线程2也可以使用资源,同时也可以让其他线程先使用资源,互相谦让,最后两个线程都无法使用资源。
问题解法:
1) 服务生解法:引人一个餐厅服务生,哲学家需经过他的允许才可以使用,因为服务生知道哪只餐叉正在使用,避免死锁
2) 资源分级解法:为资源分配一个偏序或者分级的关系,并约定所有资源都按照这个顺序获取,按相反顺序释放,可以保证不会有两个无关资源同时被同一项工作所需要。
3) Chandy/Misra解法
8. 多线程锁:
(1) 按照其性质分类:
① 公平锁/非公平锁:是否按照申请锁的顺序来获取锁。有可能会造成优先级反转或者饥饿现象
② 乐观锁/悲观锁:看待并发同步的态度
③ 独享锁/共享锁:是否被多个线程所持有
④ 互斥锁/读写锁:具体的实现。互斥锁在java中具体实现是ReentrantLock;读写锁在java中具体实现是ReentrantReadWriteLock
⑤ 可重入锁:又称递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
(2) 按照设计方案来分类:
① 自旋锁/自适应自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
② 锁粗化/锁消除:是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除/减少没必要的加锁
③ 偏向锁/轻量级锁/重量级锁:锁的状态,并且这对synchronized
④ 分段锁:其实是一种锁的设计,并不是具体的一种锁,通过分段锁的形式实现高效的并发操作
(3) 常见的锁:
Synchronized和Lock
① Synchronized:就是一个非公平、悲观,独享,互斥,可重入的重量级锁
② ReentrantLock,就是一个默认非公平但可实现公平的,悲观,独享,互斥,可重入、重量级锁
ReentrantReadWriteLock,就是一个默认非公平但可实现公平的、悲观、写独享、读独享、读写、可重入、重量级锁