生产者和消费者案例分析:生产者可以看做一个线程,消费者也可以看做一个线程
// 生产者和消费者共享一个资源
为什么生产者不直接把数据传给消费者,而是生产者先把数据存储到共享资源中,然后消费者再去共享资源中拿数据?
* 这里体现了面向对象的设计原则:
* 低耦合(个体与个体之间在能实现要求的前提下,联系越少越好)
* 高内聚
* 例如: 生产者(存储数据:姓名和性别, 如 李白-男)---->共享资源(李白-男) ---------
* - - ->消费者(取出数据:打印姓名和性别, 如 李白-男)
* 如果,共享资源中没有数据,则生产者负责产生一个数据(姓名-性别),共享资源不空了
* 否则:生产者等待消费者消费该数据
* 共享资源中没有数据,则消费者等待生产者生产数据
* 否则,消费者取出共享资源中的数据,共享资源空了
分析生产者和消费者案例存在的问题
建议在生产姓名和性别之间以及打印之前使用Thread.sleep()方法,使得问题更明显。小编推荐一个学Java的学习裙【四九八,六九一,零五三】,无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有很多干货和技术分享!
存在的问题主要有两个:
1).性别紊乱
2).生产者重复生产而消费者没有及时消费
问题1的改进方法:
1)同步代码块
2)同步方法
3)锁机制
问题2的改进方法
1)如果是在同步代码块和同步方法的前提下改进,则使用同步锁调用wait和notify()方法,即等待和唤醒机制
重复问题的解决思想:
* 生产者生产一个数据储存在共享资源中后进入等待状态并且唤醒消费者,消费者将共享资源中数据打印后,
* 唤醒生产者,再生产一个数据,消费者则进入等待状态,如果生产者一直没有生产数据,则消费者就一直处
* 于等待状态,直到生产者生产出数据,唤醒消费者为止
*
线程通信:wait和notify方法的介绍:
* java.lang.Object类 提供两类用于操作线程通信的方法
* wait();执行该方法的线程对象释放同步速,JVM把该线程存放在等待池中,等待其他线程唤醒该线程
* notify():执行该方法的线程唤醒在等待池等待的任意一个线程,把线程转移到锁池中等待
* notifyAll():执行该方法的线程唤醒在等待池等待的全部线程,把这些线程全部转移到锁池中等待
* 注意:上述方法只能被同步锁来调用,否则会出现illegalMonitorstateException异常
*
实例模式(那一个线程拿到锁那个线程才能执行X的同步方法):
* 假设A线程和B线程共同操作一个x对象(同步锁),A,B线程可以通过x对象的wait()和notify()方法来进行通信
* 流程如下:
* 1.当A线程执行X对象的同步方法时,A线程持有x对象的锁,B线程没有执行机会,B线程在X对象的锁池中等待
* 2.A线程在同步方法中执行X.wait()方法时,A线程释放X对象的锁,A线程在X对象的等待池中等待
* 3.在X对象的锁池中等待的B线程获取X对象的锁,执行X的另一个同步方法
* 4.B线程在同步方法中执行X.notify()方法时,JVM将A线程从X对象的等待池转移到X对象的锁池中,等待获取锁
* 5.B线程执行完同步方法,释放锁,A线程获得锁,继续执行同步方法
2)如果是在锁机制的前提下改进,因为没有同步锁,所以要引进condition接口
分析:
* 锁机制没有同步锁(即不能调用wait和notify()方法),该如何解决重复生产的问题?
* 解决方法:
* 引入condition接口,创建该接口对象
* 例如:
* Condition con=lock.newCondition();
* 用接口对象调用接口里面的await()方法,等待机制 相当于同步锁调用wait方法
* 用接口对象调用接口里面的signal()方法,唤醒机制 相当于同步锁调用notify方法
二.线程生命周期的六种状态
1.新建状态(new):使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前
新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已
Thread t=new Thread() 就属于新建状态
//当新建状态下的线程调用了start方法,此时从新建状态进入可运行状态
//线程对象的start方法只能调用一次,否则会报错; illegalThreadStateException
2.可运行状态(runnable):分为两种状态,ready和running. 分别表示就绪状态和运行状态
就绪状态:线程对象调用start方法后,等待JVM的调度(此刻线程并没有启动)
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行
3.阻塞状态:正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态
此时jvm不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态
阻塞状态的两种情况:
1).当A线程处于运行过程时,试图获得同步锁时,却被B线程获取,此时JVM把当前A线程存到对象的锁池里,A线程进入阻塞状态
2).当A线程处于运行状态时,发出io请求时,进入阻塞状态
4.等待状态(等待状态只能被其他线程唤醒):此时使用无参数的wait()方法
当线程处于运行状态时,调用了wait方法,此时JVM把当前线程存在对象等待池最后中
5.计时等待状态(使用了带参数的wait方法或者sleep方法)
1).当线程处于运行过程中,调用wait(long time)方法,此时JVM把当前线程存在对象的等待池中
2).当前线程调用了sleep(long )
6.终止状态:通常称之为死亡状态,表示线程终止
1).正常死亡:正常执行完run方法后终止
2).意外死亡:遇到异常而退出
三.线程的操作
1.后台线程:
* 在后台运行的线程,其目的是为其他线程提供服务,也称之为守护线程,JVM的垃圾回收线程就是典型的后台线程
* 特点:若所有的前台线程都死亡,则后台线程自动死亡,前台线程不结束,则后台线程就不会结束
* 测试线程是否为后台线程 使用Thread对象.isDaemo();如果返回false则不是,反之则是
* 前台线程创建的线程默认为前台线程,可以通过setDaemo(true)方法设置成后台线程,并且当且仅当后台线程创建
* 新线程时,新线程是后台线程
*
* 注意:
* 设置后台线程:Thread对象.setDaemo(true)该方法必须在start方法调用前使用,否则会出现illegalThread
* StateException异常
* //public final void setDaemon(boolean on)
* //public final boolean isDaemon()
2. 线程的优先级:
* 每个线程都有优先级,优先级的高低只和线程获得执行机会的多少有关,
* 与线程的执行顺序无关,并非是优先级越高就一定先执行,先执行谁取决于CPU的调度
*
* MAX_PRIORITY=10; 最高优先级
* MIN_PRIORITY=1; 最低优先级
* NORM_PRIORITY=5; 默认优先级
*
* 每个线程都有默认优先级,主线程默认优先级为5,如果A线程创建了B线程,那么A线程和B
* 线程具有相同的优先级
* 注意: 不同的操作系统支持的线程优先级不同,建议使用上述三个优先级,不要自定义
*
* Thread类中的方法:
* public final int getPriority(); 返回线程的优先级。
* public final void setPriority(int newPriority); 更改线程的优先级。
3.定时器
//TimerTask是一个抽象类,该类里面存在run方法
// new TimerTask() 代表创建一个新的计时器任务。
//public abstract void run() 代表此计时器任务要执行的操作。
* 在JDK的java.util包中提供了Timer类,可以定时执行特定的任务
* TimerTask类表示定时器执行的某一项任务
* Timer中的常用方法:
* void schedule(TimerTask,long delay,long period)
* 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
* task - 所要安排的任务。
* delay - 执行任务前的延迟时间,单位是毫秒。
* period - 执行各后续任务之间的时间间隔,单位是毫秒。
*
* void schedule(TimerTask,long delay)