Java线程的学习_线程通信

当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调 运行。

传统的线程通信

假设现在在系统中有两个线程,这两个线程分别代表上电梯和下电梯,其中,上下电梯人数必须相同。不允许两次上电梯或者两次下电梯。
为了实现这个功能,可以借助于Object类提供的wait(),notify()和notifuAll()方法,这三个方法并不不属于Thread类,而是属于Object类。但这三个方法必须由同步监视器对象来调用,,这可分成一下两种情况:
——对与使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步 方法中直接调用这三个方法。
——对于同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。

三个方法的用法:
——wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。其中该方法分为带参数和不带参数形式,带毫秒参数(等到指定时间后自动苏醒)和不带参数(一直等带,知道其他线程通知)。调用wait()方法的当前线程会释放对该同步监视器的锁定。
——notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程
——notifiAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
代码:

电梯类:

public class Lift {

    //封装电梯里的人数
    private int people;
    //标示电梯中是否已有人的标志
    private boolean flag = false;
    //构造器
    public Lift(){};
    public Lift(int people){
        this.people = people;
    }
    //电梯里的人数不随便更改,所以只提供get方法
    public int getPeople(){
        return people;
    }
    //出电梯人数的同步方法
    public synchronized void out(int outpeople){
        try{
            //如果flag为假,表明电梯中还没有人进去,进入方法阻塞
            if( !flag ){
                wait();
            }else{
                //执行出人的方法
                System.out.println(Thread.currentThread().getName() + "出电梯人数" 
                + outpeople);
                people -= outpeople;
                System.out.println("电梯内人数为:" + people);
                flag = false;
                //唤醒其他线程
                notifyAll();
            }
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    //进入电梯人数的同步方法
    public synchronized void in(int inpeople){
        try{
            //如果flag为真,表明电梯中已有人,进人方法阻塞
            if( flag ){
                wait();
            }else{
                //执行进人方法
                System.out.println(Thread.currentThread().getName() + "进入电梯人数" 
                        + inpeople);
                people += inpeople;
                System.out.println("电梯内人数为:" + people);
                flag = true;
                //唤醒其他线程
                notifyAll();
            }
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}

进电梯线程类:

public class inThread extends Thread{

    //模拟电梯
    private Lift lift;
    //进入电梯人数 
    private int inpeople;
    public inThread(String name, Lift lift, int inpeople){
        super(name);
        this.lift = lift;
        this.inpeople = inpeople;
    }
    //重复5次进入电梯
    public void run(){
        for(int i = 0; i < 5; i++){
            lift.in(inpeople);
        }
    }
}

出电梯线程类:

public class outThread extends Thread{

    //模拟电梯
    private Lift lift;
    //出入电梯人数 
    private int outpeople;
    public outThread(String name, Lift lift, int outpeople){
        super(name);
        this.lift = lift;
        this.outpeople = outpeople;
    }
    //重复5次出电梯
    public void run(){
        for(int i = 0; i < 5; i++){
            lift.out(outpeople);
        }
    }
}

主程序:

public class Test {

    public static void main(String[] args) {
        Lift l = new Lift(0);
        new outThread("5楼", l, 5).start();
        new inThread("1楼", l, 5).start();
        new inThread("2楼", l, 5).start();
        new inThread("3楼", l, 5).start();
    }

}

结果:

1楼进入电梯人数5
电梯内人数为:5
5楼出电梯人数5
电梯内人数为:0
3楼进入电梯人数5
电梯内人数为:5
5楼出电梯人数5
电梯内人数为:0
2楼进入电梯人数5
电梯内人数为:5

使用Condition控制线程通信

如果程序不是synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait(),notify().notifyAll()方法进行线程通信。
当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。
Condition将同步监视器方法(wait(),notiify().notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set)。在这种情况下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition类提供了三个方法:await().signal(),signalAll()功能分别于wait(),notiify().notifyAll()对应。

使用Condition来写电梯类:

public class Lift {

    //显式定义Lock对象
    private final Lock lock = new ReentrantLock();
    //获得指定Lock对象对应的Condition
    private final Condition cond = lock.newCondition();
    //封装电梯里的人数
    private int people;
    //标示电梯中是否已有人的标志
    private boolean flag = false;
    //构造器
    public Lift(){};
    public Lift(int people){
        this.people = people;
    }
    //电梯里的人数不随便更改,所以只提供get方法
    public int getPeople(){
        return people;
    }
    //出电梯人数的同步方法
    public void out(int outpeople){
        lock.lock();
        try{
            //如果flag为假,表明电梯中还没有人进去,进入方法阻塞
            if( !flag ){
                cond.await();
            }else{
                //执行出人的方法
                System.out.println(Thread.currentThread().getName() + "出电梯人数" 
                + outpeople);
                people -= outpeople;
                System.out.println("电梯内人数为:" + people);
                flag = false;
                //唤醒其他线程
                cond.signalAll();
            }
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
        //使用finally来释放锁
        finally{
            lock.unlock();
        }
    }
    //进入电梯人数的同步方法
    public synchronized void in(int inpeople){
        lock.lock();
        try{
            //如果flag为真,表明电梯中已有人,进人方法阻塞
            if( flag ){
                cond.await();
            }else{
                //执行进人方法
                System.out.println(Thread.currentThread().getName() + "进入电梯人数" 
                        + inpeople);
                people += inpeople;
                System.out.println("电梯内人数为:" + people);
                flag = true;
                //唤醒其他线程
                cond.signalAll();
            }
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

其他几个类不变,运行结果相同。


使用阻塞队列(BlockingQueue)控制线程通信

Java5提供了一个BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但他的主要用途并不是作为容器,而是作为线程同步的工具 ,BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue总放入元素时,若果队列已满,则该线程被阻塞 ;当消费者线程试图从BlockingQueue中取出元素时,若果队列已空,则该线程被阻塞。
程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,即可很好地控制线程的通信。

BlockingQueue提供如下两个支持阻塞的方法:
–put(E e):尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程。
–take():尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。
此外,由于BlockingQueue继承了Queue接口,所以也能使用Queue接口中的方法。

    在队列尾部插入元素。包括add(E e)、offer(e)、put(E e)方法,当队列已满时,这三个方法分别会抛出异常、返回false、阻塞队列 。
    在队列头部删除并返回删除的元素。包括remove()、poll()、take()方法。当该队列已空时,这三个方法分别会抛出异常、返回false、阻塞队列 。
    在队列头部取出但不删除元素》包括element()和peek()方法,当队列已空时这两个方法分别抛出异常、返回false。

代码示例:

class Producer extends Thread{
    private BlockingQueue<String> bq;
    public Producer(BlockingQueue<String> bq){
        this.bq = bq;
    }
    public void run(){
        String[] strArr = new String[]{
                "Java",
                "Struts",
                "Spring"
        };
        for(int i = 0; i < 20; i++){
            System.out.println(getName() + "生产者准备生产集合元素");
            try{
                Thread.sleep(200);
                //尝试放入元素,如果队列 已满,则线程被阻塞
                bq.put(strArr[i % 3]);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(getName() + "生产完成 :" + bq);
        }
    }
}
class Consumer extends Thread{
    private BlockingQueue<String> bq;
    public Consumer(BlockingQueue<String> bq){
        this.bq = bq;
    }
    public void run(){
        while(true){
            System.out.println(getName() + "消费者准备消费集合元素~");
            try{
                Thread.sleep(200);
                //尝试取出元素,如果队列已空,则线程被阻塞
                bq.take();
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(getName() + "消费完成:" + bq);
        }
    }
}
public class BlockingQueueTest2 {

    public static void main(String[] args) {
        // 创建一个容量为1的BlockingQueue
        BlockingQueue<String> bq = new ArrayBlockingQueue<String>(1);
        //启动三个生产者线程
        new Producer(bq).start();
        new Producer(bq).start();
        new Producer(bq).start();
        //创建一个消费者线程
        new Consumer(bq).start();
    }

}

上边程序启动了3个生产者线程向BlockingQueue集合放入元素,启动了一个消费者线程从BlockingQueue集合取出元素。本程序的BlockingQueue集合容量为1,因此三个生产者线程无法连续放入元素,必须等待消费者线程取出一个元素后,三个生产者线程的其中之一才能放入一个元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值