论多线程的生产者与消费者、哲学者吃饭

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) 常见的锁:

SynchronizedLock

① Synchronized:就是一个非公平、悲观,独享,互斥,可重入的重量级锁

② ReentrantLock,就是一个默认非公平但可实现公平的,悲观,独享,互斥,可重入、重量级锁

ReentrantReadWriteLock,就是一个默认非公平但可实现公平的、悲观、写独享、读独享、读写、可重入、重量级锁

猜你喜欢

转载自blog.csdn.net/qq_33171210/article/details/80196150