本章目标

  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. db2死锁分析与处理

    在数据库中,锁的主要功能是为了控制并发数据的完整性而引入的机制,在并发应用中出现锁现象并不可怕,锁现象通常分为死锁和锁等待两种情形. 死锁是因为两个并发的进程或者线程同时各自占有一个资源,又需要占有对 ...

  2. 去掉 Android工程中让人很不爽的“黄色警告”

    一:问题       二:解决方法 (1)选择android工程,右键Android Tools —> Clear Lint Markers 这种方式能够清除android工程里面的所有警告信息 ...

  3. 安卓开发_慕课网_ViewPager实现Tab(App主界面)

    学习内容来自“慕课网” 网站上一共有4种方法来实现APP主界面的TAB方法 这里学习第一种 ViewPager实现Tab 布局文件有7个, 主界面acitivity.layout <Linear ...

  4. 【转】android应用程序的安装方式与原理

    四种安装方式: 1.系统应用安装――开机时完成,没有安装界面 2.网络下载应用安装――通过market应用完成,没有安装界面 3.ADB工具安装――没有安装界面. 4.第三方应用安装――通过SD卡里的 ...

  5. 【Android开发资料分享】自己整理的Android开发资料,非常全面

    学习Android以来,不知不觉中收集了大量非常优秀的Android开发资料,一直没有系统的整理,最近抽时间把收藏夹中的资料做了一下整理,现在分享给大家,希望能够帮助到需要的人.这份资料我还会不断的更 ...

  6. NSInvocation

    NSInvocation 基本简介 NSInvocation是一个静态描绘的OC消息,也就是说,它是一个动作,这个动作可以变成一个对象.NSInvocation对象在对象和对象之间和应用程序和应用程序 ...

  7. iOS设计模式-单例模式

    (一)什么是单例模式(Singleton) 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点 *最初的定义是在<设计模式>(Addison-Wesley)中 解读 1> ...

  8. linux命令之 用户和群组

    一.保存用户信息的文件 1 /etc/passwd root:x:::root:/root:/bin/bash pwftp:x::::/alidata/www/wwwroot/:/sbin/nolog ...

  9. 深入理解java虚拟机(4)---类加载机制

    类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态 ...

  10. Linux套接字编程

    网络中的进程是如何通信的? 在网络中进程之间进行通信的时候,那么每个通信的进程必须知道它要和哪个计算机上的哪个进程通信.否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行 ...