线程间通信

参考:
JAVA多线程之线程间的通信方式
JAVA线程与线程、进程与进程间通信
对上述两篇博客进行了整理,并在JDK8下运行了实验代码进行测试。

线程间的通信方式:
1、共享变量
2、wait/notify机制
3、Lock/Condition机制
4、管道

方式一、共享变量/Shared Memory (synchronized)

这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。

package testCode;


public class MyObject {

    synchronized public void methodA() {
        //do something....
        System.out.println("A");
    }

    synchronized public void methodB() {
        //do some other thing
        System.out.println("B");
    }
}
package testCode;

public class ThreadA extends Thread {

    private MyObject object;
public ThreadA(MyObject object2) {
        // TODO Auto-generated constructor stub
    this.object = object2;
    }
    //省略构造方法
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
package testCode;

public class ThreadB extends Thread {

    private MyObject object;
public ThreadB(MyObject object2) {
        // TODO Auto-generated constructor stub
    this.object = object2;
    }
    //省略构造方法
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}
package testCode;

public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();

        //线程A与线程B 持有的是同一个对象:object
        ThreadA a = new ThreadA(object);
        ThreadB b = new ThreadB(object);
        a.start();
        b.start();
    }
}

运行结果:

A
B
由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。

这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了(获得了访问权限),谁就可以执行。

方式一、Shared Memory(while轮询的方式)

package testCode;

import java.util.ArrayList;
import java.util.List;

public class MyList {

    private List list = new ArrayList();
    public void add() {
        list.add("elements");
    }
    public int size() {
        return list.size();
    }
}
package testCode;

public class ThreadA extends Thread {

    private MyList list;

    public ThreadA(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add();
                System.out.println("添加了" + (i + 1) + "个元素");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package testCode;

public class ThreadB extends Thread {

    private MyList list;

    public ThreadB(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (list.size() == 5) {
                    System.out.println("==5, 线程b准备退出了");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
     public static void main(String[] args) {
            MyList list = new MyList();

            ThreadA a = new ThreadA(list);
            a.setName("A");
            a.start();

            ThreadB b = new ThreadB(list);
            b.setName("B");
            b.start();
        }
}

运行结果:

添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
陷入死循环….

在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。关于线程的轮询的影响,可参考:JAVA多线程之当一个线程在执行死循环时会影响另外一个线程吗?

这种方式还存在另外一个问题:

轮询的条件的可见性问题,关于内存可见性问题,可参考:JAVA多线程之volatile 与 synchronized 的比较中的第一点“一,volatile关键字的可见性”

线程都是先把变量读取到本地线程栈空间,然后再去再去修改的本地变量。因此,如果线程B每次都在取本地的 条件变量,那么尽管另外一个线程已经改变了轮询的条件,它也察觉不到,这样也会造成死循环。

方式二、wait/notify机制

package testCode;

import java.util.ArrayList;
import java.util.List;

public class MyList {

    private static List list = new ArrayList();
    public static void add() {
        list.add("elements");
    }
    public static int size() {
        return list.size();
    }
}
package testCode;

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (MyList.size() != 5) {
                    System.out.println("wait begin "
                            + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait end  "
                            + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package testCode;

public class ThreadB extends Thread {

     private Object lock;

        public ThreadB(Object lock) {
            super();
            this.lock = lock;
        }


        @Override
        public void run() {
            try {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        MyList.add();
                        if (MyList.size() == 5) {
                            lock.notify();
                            System.out.println("已经发出了通知");
                        }
                        System.out.println("添加了" + (i + 1) + "个元素!");
                        Thread.sleep(1000);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}
package testCode;

public class Run {
     public static void main(String[] args) {

            try {
                Object lock = new Object();

                ThreadA a = new ThreadA(lock);
                a.start();

                Thread.sleep(50);

                ThreadB b = new ThreadB(lock);
                b.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}

运行结果:

wait begin 1504599622985
添加了1个元素!
添加了2个元素!
添加了3个元素!
添加了4个元素!
已经发出了通知
添加了5个元素!
添加了6个元素!
添加了7个元素!
添加了8个元素!
添加了9个元素!
添加了10个元素!
wait end 1504599633035

线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。

A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?

这里用到了Object类的 wait() 和 notify() 方法。

当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。—不像②while轮询那样占用CPU

当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

这种方式的一个好处就是CPU的利用率提高了。

但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。

方法三、管道通信

就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

管道流是JAVA中线程通讯的常用方式之一,基本流程如下:

1)创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis

2)将pos和pis匹配,pos.connect(pis);

3)将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了

而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。

package testCode;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class testPipeConnection {

    public static void main(String[] args) {
        /**
         * 创建管道输出流
         */
        PipedOutputStream pos = new PipedOutputStream();
        /**
         * 创建管道输入流
         */
        PipedInputStream pis = new PipedInputStream();
        try {
            /**
             * 将管道输入流与输出流连接 此过程也可通过重载的构造函数来实现
             */
            pos.connect(pis);
        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * 创建生产者线程
         */
        Producer p = new Producer(pos);
        /**
         * 创建消费者线程
         */
        Consumer1 c1 = new Consumer1(pis);
        /**
         * 启动线程
         */
        p.start();
        c1.start();
    }
}

/**
 * 生产者线程(与一个管道输出流相关联)
 * 
 */
class Producer extends Thread {
    private PipedOutputStream pos;

    public Producer(PipedOutputStream pos) {
        this.pos = pos;
    }

    public void run() {
        int i = 0;
        try {
            while(true)
            {
            this.sleep(3000);
            System.out.println("producer: "+i);
            pos.write(i);
            i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * 消费者线程(与一个管道输入流相关联)
 * 
 */
class Consumer1 extends Thread {
    private PipedInputStream pis;

    public Consumer1(PipedInputStream pis) {
        this.pis = pis;
    }

    public void run() {
        try {
            while(true)
            {
            System.out.println("consumer1:"+pis.read());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

可以看到producer线程往consumer1线程发送数据:

producer: 0
consumer1:0
producer: 1
consumer1:1
producer: 2
consumer1:2
producer: 3
consumer1:3
producer: 4
consumer1:4
producer: 5
consumer1:5
producer: 6

管道流虽然使用起来方便,但是也有一些缺点

1)管道流只能在两个线程之间传递数据

线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。

2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流(半双工方式)

可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。

方法四、Lock/Condition机制(同步一种)

如果程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.

当使用LOCK对象保持同步时,Java为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。

假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱。
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:

首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。

package testCode;

import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  

public class Account {  

    //显示定义Lock对象  
    private final Lock lock=new ReentrantLock();  
    //获得指定Lock对象对应的条件变量  
    private final  Condition con=lock.newCondition();     

    private String accountNo;  
    private double balance;  
    //标识账户中是否有存款的旗标  
    private boolean flag=false;  

    public Account() {  
        super();  
    }  

    public Account(String accountNo, double balance) {  
        super();  
        this.accountNo = accountNo;  
        this.balance = balance;  
    }   

    public void draw (double drawAmount){  

        //加锁  
        lock.lock();  
        try {  
              if(!flag){  
//            this.wait();  
              con.await();  
             }else {  
                 //取钱  
                 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);  
                 balance=balance-drawAmount;  
                 System.out.println("余额 : "+balance);  
                 //将标识账户是否已有存款的标志设为false  
                 flag=false;  
                 //唤醒其它线程  
//               this.notifyAll();    
                 con.signalAll();  
             }  
            } catch (Exception e) {  
                e.printStackTrace();  
        }  
            finally{  
                lock.unlock();  
            }  
    }  


   public void deposit(double depositAmount){  
       //加锁  
       lock.lock();  
       try {  
              if(flag){  
//              this.wait();  
                  con.await();  
              }  
              else{  
                  System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);  
                  balance=balance+depositAmount;  
                  System.out.println("账户余额为:"+balance);  
                  flag=true;  
                  //唤醒其它线程  
//                this.notifyAll();  
                  con.signalAll();  
              }  
        } catch (Exception e) {  
            // TODO: handle exception  
            e.printStackTrace();  
        }finally{  
            lock.unlock();  
        }  
   }  

}  
package testCode;

public class DrawThread implements Runnable {  

    private Account account;  
    private double drawAmount;  


    public DrawThread(Account account, double drawAmount) {  
        super();  
        this.account = account;  
        this.drawAmount = drawAmount;  
    }  

    public void run() {  
        for(int i=0;i<4;i++){  
           account.draw(drawAmount);      
        }  
    }  
} 
package testCode;

public class depositThread implements Runnable {

    private Account account;  
    private double depositAmount;  


    public depositThread(Account account, double drawAmount) {  
        super();  
        this.account = account;  
        this.depositAmount = drawAmount;  
    }  

    public void run() {  
        for(int i=0;i<5;i++){  
           account.deposit(depositAmount);      
        }  
    }  

}
package testCode;

public class Run {
    public static void main(String[] args) {  
        //创建一个账户  
        Account account=new Account();  
        new Thread(new DrawThread(account, 800),"取钱者").start();  
        new Thread(new depositThread(account, 800),"存款者甲").start();  
        new Thread(new depositThread(account, 800),"存款者乙").start();  
        new Thread(new depositThread(account, 800),"存款者丙").start();  

    }  
}

运行结果:

存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者乙存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0

你可能感兴趣的:(java)