本章目标

  1)加深对线程同步的理解

  2)了解Object类中对线程的支持方法。

实例

  生产者不断生产,消费者不断消费产品。

  生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据。

  既然生产的是信息,就可以定义一个信息的表示类,生产者和消费者同时占有信息类的引用,那么就可以将生产者和消费者两个线程通过信息类联合在一起。

  如下:

class Info{    // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ;
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};

  建立生产者类,生产者实现多线程机制。

class Producer implements Runnable{    // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.setName("李兴华") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("JAVA讲师") ; // 设置内容
flag = false ;
}else{
this.info.setName("mldn") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("www.mldnjava.cn") ; // 设置内容
flag = true ;
}
}
}
};

  生产者生产50次信息,中间为了更好的发现问题,加入了延迟操作。

  实现消费者,消费者要不断取出。

class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
};

  最后完整代码如下  

class Info{    // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ;
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=;i<;i++){
if(flag){
this.info.setName("李兴华") ; // 设置名称
try{
Thread.sleep() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("JAVA讲师") ; // 设置内容
flag = false ;
}else{
this.info.setName("mldn") ; // 设置名称
try{
Thread.sleep() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("www.mldnjava.cn") ; // 设置内容
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=;i<;i++){
try{
Thread.sleep() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
};
public class ThreadCaseDemo01{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};

  运行结果如下:

李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> JAVA讲师
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> JAVA讲师
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
李兴华 --> www.mldnjava.cn
mldn -->
JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华
--> www.mldnjava.cn
李兴华 --> JAVA讲师

  以上结果的红色部分可以看出,已经没有按照预先结果对应了,以上代码将之前的两个问题全部暴露出来。

  之所以会出现内容不对应情况,是因为中间加入了延迟操作,所以有可能产生不同步的问题(参考上一章)。那么可以通过同步解决设置内容的问题。

  通过同步操作修改后的内容。

class Info{    // 定义信息类
private String name = "李兴华";
private String content = "JAVA讲师" ;
public synchronized void set(String name,String content){
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
}
public synchronized void get(){
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo02{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};

  运行结果如下:

李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
李兴华 --> JAVA讲师

  以上的代码解决了数据完整性问题,但是依然存在重复取的问题,既然有重复取,则肯定有重复设置的问题,

  并没有达到设置一个,取走一个的功能要求。

  如果要想采用以上这种机制,则必须依靠Object类中的方法的支持。

 Object类对线程的支持--等待与唤醒机制

  Object类是所有类的父类,此类有以下几种方法是对线程操作有所支持的。

  

  唤醒有两个方法:notify()和notifyAll()方法。

  注意,因为Object是所有类的父类,所以其他类要想使用等待和唤醒,直接使用super即可,super调用父类方法。super.wait();super.notify();

  notify()会唤醒第一个等待的线程,notifyAll()会全部唤醒,那个线程优先级高哪个先执行。

  注意wait()和notify()方法跟sleep()方法一样,需要异常处理

  

  直接修改Info类即可,增加等待与唤醒的机制。

wait() :

使当前线程等待直到另外一个线程调用了这个对象的Object的nofity()方法或者Object的notifyAll()方法。

复习synchronized知识可以参考:

java线程同步: synchronized详解(转)

线程的同步与死锁

可以单这里:

Java 多线程详解(四)------生产者和消费者

wait():执行该方法的线程对象,释放同步锁,JVM会把该线程放到等待池中,等待其他线程唤醒该线程

notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待(注意锁池和等待池的区别)

notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。

注意:上述方法只能被同步监听锁对象来调用,这也是为啥wait() 和 notify()方法都在 Object 对象中,因为同步监听锁可以是任意对象,只不过必须是需要同步线程的共同对象即可,否则别的对象调用会报错:        java.lang.IllegalMonitorStateException

假设 A 线程和 B 线程同时操作一个 X 对象,A,B 线程可以通过 X 对象的 wait() 和 notify() 方法来进行通信,流程如下:

①、当线程 A 执行 X 对象的同步方法时,A 线程持有 X 对象的 锁,B线程在 X 对象的锁池中等待

②、A线程在同步方法中执行 X.wait() 方法时,A线程释放 X 对象的锁,进入 X 对象的等待池中

③、在 X 对象的锁池中等待锁的 B 线程获得 X 对象的锁,执行 X 的另一个同步方法

④、B 线程在同步方法中执行 X.notify() 方法,JVM 把 A 线程从等待池中移动到 X 对象的锁池中,等待获取锁

⑤、B 线程执行完同步方法,释放锁,等待获取锁的 A 线程获得锁,继续执行同步方法

package Thread1;
class Info{ // 定义信息类
private String name = "李兴华";  private String content = "JAVA讲师" ; //设置了默认值,使得消费者最开始就能消费。
  private boolean flag = false ; // 设置标志位,默认为false,使得消费者能消费,生产者一开始就进入等待,直到消费者消费完唤醒它
public synchronized void set(String name,String content)throws InterruptedException{if(!flag){ 1
super.wait() ;  2 //如果flag为false,该进程进入等待,不往下进行,等待消费者消费完了,执行notify才能唤醒自己,继续下面的操作
}
this.setName(name); // 设置名称
Thread.sleep(300) ;
this.setContent(content) ; // 设置内容
flag = false ; // 设置为false,目的是让调用本方法的线程不会第二次执行(第二次调用的时候就会把自己wait住,直到消费者消费完了,
    才能唤醒自己,才能继续执行生产)

super.notify() ;  //生产完了,唤醒正在等待消费的消费者。
}
public synchronized void get()throws InterruptedException{
if(flag){
super.wait() ;
}
Thread.sleep(300) ;
System.out.println(this.getName() +
" --> " + this.getContent()) ;
flag = true ; // 改变标志位,表示可以生产
super
.notify() ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
try{
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}catch(InterruptedException e)  //处理异常
{
e.printStackTrace();
}

}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
try{
this.info.get() ;
}catch(InterruptedException e)  //处理异常
{
e.printStackTrace();
}

}
}
};
public class demo1{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};

运行结果:

李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn
李兴华 --> JAVA讲师
mldn --> www.mldnjava.cn

分析:

最开始设置Info对象的flag为false,使得生产者一进去就进入等待wait,消费者可以消费,此时消费者消费的是info对象默认值,消费完了设置flag为true,使得自己再次消费

的话,自己就进入等待(wait),等待生产者生产完告诉自己(唤醒它),然后告诉生产者可以开始生产了(唤醒生产者),此时等待中的生产者唤醒,获取锁,继续往下执行,生产完了后,flag设置为false,防止自己再次相生产的时候,可以把自己进入等待状态(直到消费者消费完告诉自己(唤醒自己)),然后告诉消费者,我生产完了,你可以开始消费了(唤醒消费者),此时等待中的消费者被唤醒,开始执行消费。。。循环。

此时等待的消费者开始继续

总结

  1,在本程序中需要注意以下两种问题:

  1)生产者要不断生产信息,但是不能重复生产,或者生产错误信息。

  2)消费者要不断取走,但是不能重复取走。

  2,Object类对线程的支持,等待wait()和nofity()和notifyAll(),注意异常的处理。

  3,本道程序只是加深同步,等待,唤醒机制操作印象。

线程操作案例--生产者与消费者,Object类对线程的支持的更多相关文章

  1. 吴裕雄--天生自然java开发常用类库学习笔记:线程操作案例——生产者与消费者

    class Info{ // 定义信息类 private String name = "李兴华"; // 定义name属性 private String content = &qu ...

  2. java 线程并发(生产者、消费者模式)

    线程并发协作(生产者/消费者模式) 多线程环境下,我们经常需要多个线程的并发和协作.这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”. Ø 什么是生产者? 生产者指的是负责生产数 ...

  3. 多进程(了解):守护进程,互斥锁,信号量,进程Queue与线程queue(生产者与消费者模型)

    一.守护进程 主进程创建守护进程,守护进程的主要的特征为:①守护进程会在主进程代码执行结束时立即终止:②守护进程内无法继续再开子进程,否则会抛出异常. 实例: from multiprocessing ...

  4. Java 线程的通讯--生产者和消费者

    package 生产者和消费者; //消费者 public class Customer implements Runnable { private Share_resources rescource ...

  5. JAVA线程通信之生产者与消费者

    package cn.test.hf.test3; import java.util.concurrent.locks.Condition;import java.util.concurrent.lo ...

  6. 并发编程(六)Object类中线程相关的方法详解

    一.notify() 作用:唤醒一个正在等待该线程的锁的线程 PS : 唤醒的线程不会立即执行,它会与其他线程一起,争夺资源 /** * Object类的notify()和notifyAll()方法详 ...

  7. C#操作SqlServer MySql Oracle通用帮助类Db_Helper_DG(默认支持数据库读写分离、查询结果实体映射ORM)

    [前言] 作为一款成熟的面向对象高级编程语言,C#在ADO.Net的支持上已然是做的很成熟,我们可以方便地调用ADO.Net操作各类关系型数据库,在使用了多年的Sql_Helper_DG后,由于项目需 ...

  8. WinForm中新开一个线程操作 窗体上的控件(跨线程操作控件)

    最近在做一个winform的小软件(抢票的...).登录窗体要从远程web页面获取一些数据,为了不阻塞登录窗体的显示,开了一个线程去加载数据远程的数据,会报一个错误"线程间操作无效: 从不是 ...

  9. java 中多线程之间的通讯之生产者和消费者 (多个线程之间的通讯)

    在真实开发 中关于多线程的通讯的问题用到下边的例子是比较多的 不同的地方时if 和while 的区别 如果只是两个线程之间的通讯,使用if是没有问题的. 但是在多个线程之间就会有问题 /* * 这个例 ...

随机推荐

  1. Angular 核心概念

    module(模块) 作用 通过模块对页面进行业务上的划分,根据不同的功能划分不同的模块. 将重复使用的指令或者过滤器之类的代码做成模块,方便复用 注意必须指定第二个参数,否则变成找到已经定义的模块 ...

  2. 微信公共平台开发5 .net

    每次在于微信交互时,都要用到access_token,但是这个值限制的是有时间的,但是access_token,在以后的高级功能里面会经常用到,所以这里不得不这里对前面所讲解的access_token ...

  3. 利用ng-click、ng-switch和click-class制作切换的tabl

    效果如下图,当分别点击1,2,3时,下面的不同颜色的div会切换 <html ng-app> <head> <title></title> <sc ...

  4. ABAP modify screen:修改屏幕,实现隐藏、禁止输入字段

    Loop at screen会loop处理屏幕上的每一个组件,并对其做相应的处理. SELECTION-SCREEN: BEGIN OF BLOCK B1 WITH FRAME.PARAMETERS  ...

  5. ESRI.ArcGIS.esriSystem名称空间问题

    在AO或AE开发中,并没有ESRI.ArcGIS.esriSystem这个dll,只有ESRI.ArcGIS.System,凡是需要ESRI.ArcGIS.esriSystem命名空间时,添加ESRI ...

  6. Atitit.木马 病毒 免杀 技术 360免杀 杀毒软件免杀 原理与原则 attilax 总结

    Atitit.木马 病毒 免杀 技术 360免杀 杀毒软件免杀 原理与原则 attilax 总结 1. ,免杀技术的用途2 1.1. 病毒木马的编写2 1.2. 软件保护所用的加密产品(比如壳)中,有 ...

  7. ArcGIS 10 SP5中文版(ArcGIS10补丁5中文版)

    下载地址:百度网盘下载地址:http://pan.baidu.com/s/1o7qPGhk 来自:http://zhihu.esrichina.com.cn/?/sort_type-new__day- ...

  8. 学写了一段LINQ

    要写一段代码读入一个用空格分隔的几列的文件,程序中有多处类似的文件,所以想着有没有什么好点的办法. 井名       X坐标       Y坐标         深度 测试井1  634600    ...

  9. 招聘一个靠谱的 iOS程序员

    一个靠谱的简历 简历非常能反映一个人的性格和水平,相比于你在学校获得多少奖项,工作经历.项目经 历.熟悉的技术等更加关键,如果还有博客和一些 Github 上的项目,好感度++,但记得在去面试前收拾下 ...

  10. Android UI之下拉刷新上拉刷新实现

    在实际开发中我们经常要用到上拉刷新和下拉刷新,因此今天我写了一个上拉和下拉刷新的demo,有一个自定义的下拉刷新控件 只需要在布局文件中直接引用就可以使用,非常方便,非常使用,以下是源代码: 自定义的 ...