Java线程通信

当线程在系统内部运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但我们可以通过一些机制来保证线程协调运行

线程的协调运行

为了实现这种功能,可以借助Object类提供的wait().notify()和notifyAll()三个方法,这三个方法并不属于Thread类,而是属于Object类,所以这三个方法必须由同步监视器来对象来调用,分为以下两种情况:

  • 对于使用synchronized修饰的同步方法,因为同步监视器是this,所以可以直接使用这三个方法
  • 对于使用synchronized修饰的同步代码块,必须使用同步对象来调用这三个对象

关于这三个方法的解释如下:

  • wait(): 导致当前线程等待,直到其他线程调用该同步监视器notify()或notifyAll()方法来唤醒该线程.无时间参数的wait()(一直等待,直到其他线程通知),带时间参数的wait()(等待指定时间后自动苏醒).调用wait()方法的当前线程会释放对该同步监视器的锁定.
  • notify(): 唤醒在此同步监视器上等待的单个线程,如果所有线程都在该监视器上等待,则任意唤醒其中一个
  • notifyAll(): 唤醒所有在此同步监视器上等待的线程
    (注意被notify唤醒的线程会从上次wait的地方开始执行,并不是从头开始执行)

借用之前的一片文章线程安全中的银行取钱问题来演示,现在有两条线程,一个取钱,一个存钱;这两个线程交替执行,任何一个不允许连续执行两次,程序中用一个flag来标是否有存款

账户

public class Account {

    private int account;
    private String name;
    private Boolean flag = false;

    public Account(String name, int account) {
        this.account = account;
        this.name = name;
    }

    public synchronized void draw(int money) {

        try {
            while (!flag) {//flag=false表示还没有人把钱存进去,则阻塞线程
                wait();
            }
            account = account - money;
            System.out.println(Thread.currentThread().getName() + "取钱成功!" + " 取出" + money + ",剩余" + account);
            flag = false;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void deposit(int money) {
        try {
            while (flag) {//flag=true表示已经有人把钱存进去了,不可以再存了,阻塞线程
                wait();
            }
            account = account + money;
            System.out.println(Thread.currentThread().getName() + "存钱成功!" + " 存入" + money + ",剩余" + account);
            flag = true;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意wait那里用一个无限循环来判断,因为被notify唤醒时会从wait的地方从执行,所以还要判断flag
取钱线程

public class DrawThread extends Thread{

    private Account account;
    private int draw;

    public DrawThread( String threadname, Account account,int draw){
        super(threadname);
        this.account=account;
        this.draw=draw;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.draw(draw);
        }
    }
}

存钱线程

public class DepositThread extends Thread{
    private Account account;
    private int deposit;
    public DepositThread(String name,Account account,int deposit){
        super(name);
        this.account=account;
        this.deposit=deposit;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.deposit(deposit);
        }
    }
}

测试

public class TestThread {

    public static void main(String []args){
        Account account=new Account("Eric",600);

        DrawThread A=new DrawThread("A",account,600);
        A.start();
        DrawThread B=new DrawThread("B",account,600);
        B.start();

        DepositThread C=new DepositThread("C",account,600);
        C.start();
        DepositThread D=new DepositThread("D",account,600);
        D.start();
    }
}

输出结果:

D存钱成功! 存入600,剩余1200
A取钱成功! 取出600,剩余600
D存钱成功! 存入600,剩余1200
B取钱成功! 取出600,剩余600
D存钱成功! 存入600,剩余1200
A取钱成功! 取出600,剩余600

结果是正确的,取钱和存钱交替执行,也没有出现账户余额不正常的问题.

使用条件变量控制协调

如果程序使用Lock对象来保证同步,则系统中不存在同步监视器对象,也就不能使用wait(),notify(),notifyAll()来协调线程的运行.

当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程.

Condition实例实质被绑定在一个Lock对象上.要获得特定Lock对象的Condition实例,调用Lock对象newCondition()即可.Condition提供如下三个方法来协调线程

  • await(): 类似于wait()方法,导致当前线程等待,await()有更多的变体,ondition.awaitNanos(long nanosTimeout);ondition.awaitUntil(Date deadline);ndition.awaitUninterruptibly()等可以完成更丰富的操作
  • singal(): 唤醒在此Lock对象上等待的单个线程.类似notify任意唤醒其中的一个线程
  • singalAll(): 唤醒在此Lock对象上等待的所有线程,类似notifyAll

接下来演示一下,修改Account,使用Lock对象来控制同步,Condition对象来控制线程的协调运行

public class Account {

    private int account;
    private String name;
    private Boolean flag = false;
    private final ReentrantLock lock=new ReentrantLock();
    private final Condition condition=lock.newCondition();

    public Account(String name, int account) {
        this.account = account;
        this.name = name;
    }

    public void draw(int money) {
        lock.lock();
        try {
            while (!flag) {//flag=false表示还没有人把钱存进去,则阻塞线程
                condition.await();
            }
            account = account - money;
            System.out.println(Thread.currentThread().getName() + "取钱成功!" + " 取出" + money + ",剩余" + account);
            flag = false;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void deposit(int money) {
        lock.lock();
        try {
            while (flag) {//flag=true表示已经有人把钱存进去了,不可以再存了,阻塞线程
                condition.await();
            }
            account = account + money;
            System.out.println(Thread.currentThread().getName() + "存钱成功!" + " 存入" + money + ",剩余" + account);
            flag = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

测试运行正确,Lock和Condition搭配显示的进行了控制

使用管道流

前面的两种方法不算是线程之间真正的通信,称之为协调运行策略更准确.如果两条线程之间进行更多的信息交互,则可以考虑使用管道流进行通信,管道流有3种存在形式

  • PipedInputStream和PipedOutputStream 字节流
  • PipeReader和PipeWriter 字符流
  • Pipe.SinkChannel和Pipe.SourceChannel

使用管道流实现多线程通信可按照一下步骤

  1. 使用new创建管道输入流和管道输出流.
  2. 使用管道输入流或管道输出流的connect方法把两个输入流和输出流连接起来.
  3. 将管道输入,输出流分别传入两个线程
  4. 两个线程根据自己得到的管道输入流, 管道输出流进行通信

下面来进行演示

public class ReaderThread extends Thread {
    private PipedReader pipedReader;
    private BufferedReader bufferedReader;

    public ReaderThread(PipedReader pipedReader) {
        this.pipedReader = pipedReader;
        this.bufferedReader = new BufferedReader(pipedReader);
    }

    @Override
    public void run() {
        String line = null;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class WriterThread extends Thread {
    private PipedWriter pipedWriter;
    private String[] args =
                    {"我是小红",
                    "ABCDEFG",
                    "我是小李",
                    "abcdefg"};

    public WriterThread(PipedWriter pipedWriter) {
        this.pipedWriter = pipedWriter;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                pipedWriter.write(args[i % 4] + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                pipedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String []args){
        PipedWriter pipedWriter;
        PipedReader pipedReader;
        try{
            pipedWriter=new PipedWriter();
            pipedReader=new PipedReader();
            //连接两个管道流
            pipedWriter.connect(pipedReader);
            new ReaderThread(pipedReader).start();
            new WriterThread(pipedWriter).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

ABCDEFG
我是小李
abcdefg
我是小红
ABCDEFG
我是小李
abcdefg

其中管道流的输入输出和java的IO操作一样

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值