java线程间通信

首先看一段代码

class Res
{
String name;
String sex;
}
class Input implements Runnable
{
private Res r; Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true){
if(x==0){
r.name = "mike";
r.sex = "male";
}
else{
r.name = "丽丽";
r.sex = "女";
}
x = (x+1) % 2;
}
}
} class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
System.out.println(r.name + "---" + r.sex);
}
}
} class Test
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r); Thread t1 = new Thread(in);
Thread t2 = new Thread(out); t1.start();
t2.start();
} }

上面的代码主要是想实现一个人名和姓别的同时输入和输出,但是看结果却是错乱不堪的。这个原因是由于是线程安全问题。

下面就代码中加入同步代码块。关键要注意到Input和Output都要加入,并且synchronized的对象必须是同一个,这个选取Res r = new Res(); 由这条语句创建的对象r比较好。下面是修改后的代码及结果。

class Res
{
String name;
String sex;
}
class Input implements Runnable
{
private Res r; Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true){
synchronized(r){
if(x==0){
r.name = "mike";
r.sex = "male";
}
else{
r.name = "丽丽";
r.sex = "女";
}
x = (x+1) % 2;
}
}
}
} class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r){
System.out.println(r.name + "---" + r.sex);
}
}
}
} class Test
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r); Thread t1 = new Thread(in);
Thread t2 = new Thread(out); t1.start();
t2.start();
} }

同步代码块保证线程安全运行,但是为什么会出现一直显示丽丽或者是mike呢?这是由于假如输入线程输入之后输出线程一直在执行,那么输出的就是相同的内容了,这肯定不是我们想要的,我们想要的是输入一组数据就输出一组数据,那么如何修改呢?其实我们加一个标记用于判断,当没有输入的时候不能取数据而只能存数据,当已经存入一组数据的时候只能取数据而不能存数据。

class Res
{
String name;
String sex;
boolean flag = false; //这里主要是一个标志,判断是否有输入,初始化为没有输入数据
}
class Input implements Runnable
{
private Res r; Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true){
synchronized(r){ //同步代码块
if(r.flag) //如果本身有数据就等待,并且通知其他线程取走数据
try{r.wait();}catch(Exception e){}//在这里其实会阻塞,不会往下执行
if(x==0){
r.name = "mike";
r.sex = "male";
}
else{
r.name = "丽丽";
r.sex = "女";
}
x = (x+1) % 2;
//到这里说明该线程阻塞后其他线程已经取走数据,并且告知该线程,该线程又可存数据
r.flag = true; //存数据之后标记为改变
try{r.notify();}catch(Exception e){}
}
}
}
} class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r){
if(!r.flag)//这里如果没有数据就会等待,有数据就会去取数据
try{r.wait();}catch(Exception e){}
System.out.println(r.name + "---" + r.sex);
r.flag = false;//取了数据就换标记位
try{r.notify();}catch(Exception e){}//然后通知其他线程
}
}
}
} class Test
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r); Thread t1 = new Thread(in);
Thread t2 = new Thread(out); t1.start();
t2.start();
} }

此时就会出现我们想要的结果,看看java api

可以看出wait(),notify(),等方法都用在同步中,因为要对持有锁的操作。所以这些方法要在同步中,只有同步才有锁!

为什么这些方法还会定义在Object中呢?

因为这些线程在操作同步线程时,都必须标识他们所操作线程的锁。只有同一个锁上被wait的线程才可以被同一个锁上的notify唤醒!也就是等待和唤醒必须是同一把锁,而锁可以是任意对象,而任意对象就定义在Object中!

现在对上面的代码进行优化。

class Res
{
private String name;
private String sex;
private boolean flag = false; //这里主要是一个标志,判断是否有输入,初始化为没有输入数据
public synchronized void set(String name,String sex){
if(flag) //如果本身有数据就等待,并且通知其他线程取走数据
try{this.wait();}catch(Exception e){}//在这里其实会阻塞,不会往下执行
this.name = name;
this.sex = sex;
flag = true; //存数据之后标记为改变
try{this.notify();}catch(Exception e){}
}
public synchronized void out(){
if(!flag)//这里如果没有数据就会等待,有数据就会去取数据
try{this.wait();}catch(Exception e){}
System.out.println(name + "---" + sex);
flag = false;//取了数据就换标记位
try{this.notify();}catch(Exception e){}//然后通知其他线程 }
}
class Input implements Runnable
{
private Res r;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true){
if(x==0)
r.set("mike","male");
else
r.set("丽丽","女");
x = (x+1) % 2;
}
}
}
class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class Test
{
public static void main(String[] args)
{
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
} }

这次再来看一个生产消费者的例子。

class  ProducerConsumer
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
} class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){ //这里传入一个参数,生产一个商品
if(flag)
try{wait();}catch(Exception e){}
this.name = name + "----" + count++; //生产一个商品,进行计数
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
flag = true;
this.notify();
}
public synchronized void out(){//消费一个商品
if(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"---消费者---"+this.name);
flag = false;
this.notify();
}
} class Producer implements Runnable
{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while(true){
res.set("+商品+");
}
}
} class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while(true){
res.out();
}
}
}

但是如果主函数的代码改为如下:

public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}

这里就是有两个生产线程,有两个消费线程,那么会出现问题

这就是当出现多个线程的时候的问题,需要使用while()循环不断判断标记,而不能使用if进行单次判断。也就是要把set和out里面的if语句全部变为while。这个时候会发生全部等待的现象,这里需要使用notifyAll进行全部唤醒。上面的程序只需要修改一下的几个地方

public synchronized void set(String name){
while(flag)//(1)
try{wait();}catch(Exception e){}
this.name = name + "----" + count++;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
flag = true;
this.notifyAll();//(2)
}
public synchronized void out(){
while(!flag)//(3)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
flag = false;
this.notifyAll();//(4)
}

记住:

当出现多个生产者线程和消费者线程时,必须使用while和notifyAll。

其实notifyAll唤醒了所有线程,这也不是我们的目的,我们只想唤醒对方线程,这该怎么做呢?

其实这需要看jdk1.5的新特性

仔细看这方面的资料,重新修改代码

import java.util.concurrent.locks.*;
class ProducerConsumer
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
} class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); public void set(String name)throws InterruptedException{
lock.lock();//这里显示加锁
try {
while(flag)
condition.await();// == try{wait();}catch(Exception e){}
this.name = name + "----" + count++;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
flag = true;
condition.signalAll();// == this.notifyAll();如果使用signal则会出现等待现象
} finally {
lock.unlock();//这里显示解锁,必须执行,所以要放在这里
} }
public void out()throws InterruptedException{
lock.lock();//这里显示加锁
try {
while(!flag)
condition.await();// == try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
flag = false;
condition.signalAll();// == this.notifyAll();如果使用signal则会出现等待现象
} finally {
lock.unlock();//这里显示解锁,必须执行,所以要放在这里
}
}
} class Producer implements Runnable
{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while(true){
try{res.set("+商品+");}catch(InterruptedException e){}
}
}
} class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while(true){
try{res.out();}catch(InterruptedException e){}
}
}
}

上面程序只是对上上一个程序的替代,只不过是用到了比价现代的做法,但是本质还是没变,没有达到我们的目的,就是唤醒线程只唤醒对方线程,简而言之,生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。虽然上面的程序没有实现这个目标,不过他具有比较多的特性,现在只需要简单修改便可达到目的。

class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition(); public void set(String name)throws InterruptedException{
lock.lock();
try {
while(flag)
condition_pro.await();// == try{wait();}catch(Exception e){}
this.name = name + "----" + count++;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
flag = true;
condition_con.signal();// == this.notifyAll();
} finally {
lock.unlock();
} }
public void out()throws InterruptedException{
lock.lock();
try {
while(!flag)
condition_con.await();// == try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
flag = false;
condition_pro.signal();// == this.notifyAll();
} finally {
lock.unlock();
}
}
}

这里主要看注释的几行代码。他们达到了目的:生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。lock可以支持多个相关的 Condition 对象。

java线程详解(三)的更多相关文章

  1. java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  2. Java线程详解----借鉴

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  3. 【转】Java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  4. java线程——详解Callable、Future和FutureTask

    回顾: 接上篇博客 java线程--三种创建线程的方式,这篇博客主要介绍第三种方式Callable和Future.比较继承Thread类和实现Runnable接口,接口更加灵活,使用更广泛.但这两种方 ...

  5. 并发编程 || Java线程详解

    通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...

  6. java线程详解(二)

    1,线程安全 先看上一节程序,我们稍微改动一下: //线程安全演示 //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下 class Ticket implements Run ...

  7. java线程详解(一)

    1,相关概念简介 (1)进程:是一个正在执行的程序.每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元.用于分配空间. (2)线程:就是进程中一个独立的控制单元,线程在控制着 ...

  8. Java多线程详解(三)

    1)死锁 两个线程相互等待对方释放同步监视器时会出现死锁的现象,这时所有的线程都处于阻塞状态,程序无法继续向下执行. 如下就是会出现死锁的程序. 首先flag = 1,线程d1开始执行,锁住对象o1, ...

  9. “全栈2019”Java多线程第二十五章:生产者与消费者线程详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. XE6移动开发环境搭建之IOS篇(9):配置XE6的IOS SDK(有图有真相)

    网上能找到的关于Delphi XE系列的移动开发环境的相关文章甚少,本文尽量以详细的图文内容.傻瓜式的表达来告诉你想要的答案. 原创作品,请尊重作者劳动成果,转载请注明出处!!! 1.开启PAServ ...

  2. MMU

    1.MMU功能 将虚拟地址转化为物理地址:访问权限管理. 2.地址转化 2.1 总体分析 2.2 一级转换格式 位解释:(段设置) B:表示是否使能write buffer: C:  表示是否开启ca ...

  3. uva 1368 DNA Consensus String

    这道题挺简单的,刚开始理解错误,以为是从已有的字符串里面求最短的距离,后面才发现是求一个到所有字符串最小距离的字符串,因为这样的字符串可能有多个,所以最后取最小字典序的字符串. 我的思路就是求每一列每 ...

  4. zedboard如何从PL端控制DDR读写(七)

    前面已经详细介绍了从PL端如何用AXI总线去控制DDR的读写,并且从AXI_BRESP的返回值来看,我们也是成功写入了的.但是没有通过别的方式来验证写入的数据的话,总是感觉差了点什么. 今天试了一把从 ...

  5. phpexcel简单用法

    <?php /*php生成excel完整实例代码现求:php生成excel完整实例代码最好能说明如何调用!谢谢java_sunhui4 | 浏览 8131 次 2014-09-24 14:502 ...

  6. ServiceLocator 简单示例(转)

    Service Locator Pattern in C#: A Simple Example(转) Service Locator Pattern in C# with Lazy Initializ ...

  7. c 数据拼接

    char buf1[] = {0x31,0x32,0x33,0x00,0x51,0x52,0x53,0xaa,0xbb,0xcc,0x00}; int a=0xabcd6799; int b=0x88 ...

  8. 关于Nios II的启动分析(转载)

    原文地址:http://hi.baidu.com/goatdai/item/cc33671545d89243e75e06ad 常用到的存储器包括SDRMA.SRAM.FLASH.Onchip_memo ...

  9. C# 根据包含文件的路径和文件的名称的字符串获取文件名称的几种方法

    C# 截取带路径的文件名字,扩展名,等等 的几种方法 C#对磁盘IO操作的时候,经常会用到这些,路径,文件,文件名字,文件扩展名. 之前,经常用切割字符串来实现, 可是经常会弄错. 尤其是启始位置,多 ...

  10. 在VC项目中使用自定义资源

    这是我看到的比较实用性的技巧,分享给大家 一.前言 在VC环境中除了我们所常用的Dialog.Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自 ...