Java多线程 -- 线程通信

Java多线程 – 线程通信

一、传统的线程通信

假设现在系统中有两个线程,交替打印奇偶数。
为了实现这个功能,可以借助于Object类的wait(),notify(),notifyAll()三个方法,这三个方法并不属于Thread类,而是属于Object类。但是这三个方法必须由同步监视器对象来进行调用。

可以分为两种情况:

  1. 对于使用了synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法之中直接调用三个方法。
  2. 对于使用了synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用对象调用这三个方法。

这三种方法:

  • wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或者notifyAll()方法来唤醒该线程,其中wait()方法有三种形式,无参数的(一直等待,直到其他线程通知),带毫秒的,以及带微妙的参数,(这两个方法是等待指定时间后自动属性),调用wait()方法的当前线程会释放对该同步监视器的锁定。
  • notify():唤醒在此同步监视器上等待的耽搁线程,如果说所有的线程都在此同步监视器上等待,则会唤醒其中一个线程,选择是任意性的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的进程。
  • notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的进程。

public class Demo {
    public int start = 1;//定义共享变量
    public boolean flag = false;//定义标识

    public static void main(String[] args) {
        Demo d = new Demo();//新建一个对象实例
        OddNum oddNum = new OddNum(d);
        EvenNum evenNum = new EvenNum(d);
        Thread oddNumThread = new Thread(oddNum);
        Thread evenNumThread = new Thread(evenNum);
        oddNumThread.start();
        evenNumThread.start();
    }



}


class OddNum implements Runnable{
    private Demo demo;
    OddNum(Demo demo){
        this.demo = demo;
    }
    @Override
    public void run(){
        while (demo.start < 100){
            synchronized (Demo.class){
                if(!demo.flag){
                    System.out.println("奇数线程——" + demo.start);
                    demo.start++;
                    demo.flag = true;
                    Demo.class.notify();//让另一线程苏醒
                }else{
                    try {
                        Demo.class.wait();//本线程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

class EvenNum implements Runnable{
    private Demo demo;
    EvenNum(Demo demo){
        this.demo = demo;
    }
    @Override
    public void run(){
        while(demo.start < 100){
            synchronized (Demo.class){
                if(demo.flag){
                    System.out.println("偶数线程——" + demo.start);
                    demo.start++;
                    demo.flag = false;
                    Demo.class.notify();
                }else{
                    try{
                        Demo.class.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

二、使用Condition控制线程通信

如果程序不采用synchronized关键字来保证同步,而是采用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不可以使用wait(),notify(),notifyAll()方法进行线程通信了。
但是Java使用了Condition类来保持协调,使用Condition可以让那些已经得到的Lock对象无法继续执行线程数释放Lock对象,Condition对象也可以唤醒其他在等待的线程、
Condition将同步监视器方法(wait()notify()notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set),这种情况之下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。

Condition实例被绑定在一个Lock对象上,要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可,Condition类提供了三个方法:

  • await():类似隐式同步监视器上的wait()方法,导致当前线程等待,指导其他线程调用该Conditionsignal()sianalAll()方法来唤醒该线程。
  • signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该Lock对象的锁定后(await()方法),才可以执行被唤醒的线程。
  • signalAll():唤醒在此Lock对象上等待的所有线程,只有当前线程放弃对该Lock对象的锁定后(await()方法),才可以执行被唤醒的线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo{
    Lock lock = new ReentrantLock();
    Condition oddCondition = lock.newCondition();
    Condition evenCondition = lock.newCondition();
    boolean flag = false;
    int start = 1;

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread odd = new Thread(new OddNum(demo));
        Thread even = new Thread(new EvenNum(demo));
        odd.start();
        even.start();
    }
}

class OddNum implements Runnable{
    private Demo demo;

    OddNum(Demo demo){
        this.demo = demo;
    }

    @Override
    public void run(){
        while (demo.start < 100){
            if(!demo.flag){
                demo.lock.lock();
                System.out.println(demo.start);
                demo.start++;
                demo.evenCondition.signal();
            }else{
                try {
                    demo.oddCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    demo.lock.unlock();
                }
            }
        }
    }
}

class EvenNum implements Runnable{
    private Demo demo;

    EvenNum(Demo demo){
        this.demo = demo;
    }

    @Override
    public void run(){
        while (demo.start < 100){
            if(demo.flag){
                demo.lock.lock();
                System.out.println(demo.start);
                demo.start++;
                demo.oddCondition.signal();
            }else{
                try {
                    demo.evenCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    demo.lock.unlock();
                }
            }
        }
    }
}

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

Java 5提供了一个BlockingQueue借口,虽然BlockingQueue也是Queue的子接口,但它主要用途不是容易,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者试图向其中放入元素的时候,如果该队列已经满了,则线程会阻塞,当消费者试图从其中去除元素,当队列为空,则该线程被阻塞。
BlockingQueue提供如下两个支持阻塞的方法:
put(E e):将E元素放入,如果已满,阻塞该线程。
take():尝试从头部去除元素,如果已空,则阻塞之。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章