Java 多线程通信

线程间的通信

如果你知道进程间的通信,那么对你来说,线程间的通信将会很容易,当你在开发一个产品当时候,如果涉及到两个或多个线程间交换信息,那么线程间的通信知识对你来说,将会非常重要。

下面有三个简单的方法和技巧来让线程间的通信成为可能:

序号 方法描述
1 public void wait();导致当前线程阻塞,知道另外一个线程执行notify()方法
2 public void notify();唤醒一个对象监听器上正在等待的线程
3 public void notifyAll();唤醒所有在同一个对象上执行过wait()方法的线程

这些方法在Object对象中是作为final类型实现的,所以这些方法在所有的类里都可以使用,以上三个方法,只能在synchronized上下文中执行;

以下的这个例子,将向你展示如何在两个线程之间,通过wait()和notify()这两个方法实现通信。

定义Chat类

/**
 * Created by choleece on 2019/4/20.
 */
public class Chat {
    boolean flag = false;

    public synchronized void question(String msg) {
        if (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(msg);
        flag = true;
        notify();
    }

    public synchronized void answer(String msg) {
        if (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(msg);
        flag = false;
        notify();
    }
}

定义T1类

/**
 * Created by choleece on 2019/4/20.
 */
public class T1 implements Runnable {
    Chat m;
    String[] s1 = {"Hi!I am T1", "How are you?", "I am also doing fine!"};

    public T1(Chat m1) {
        this.m = m1;
        new Thread(this, "question").start();
    }

    @Override
    public void run() {
        for (int i = 0; i < s1.length; i++) {
            m.question(s1[i]);
        }
    }
}

定义T2类

/**
 * Created by choleece on 2019/4/20.
 */
public class T2 implements Runnable {
    Chat m1;
    String[] s2 = {"Hi!I am T2", "I am good, What about you?", "Great!"};

    public T2(Chat m1) {
        this.m1 = m1;
        new Thread(this, "answer").start();
    }

    @Override
    public void run() {
        for (int i = 0; i < s2.length; i++) {
            m1.answer(s2[i]);
        }
    }
}

定义TestThread类

/**
 *
 * @author choleece
 * @date 2019/4/20
 */
public class TestThread {

    public static void main(String[] args) {
        Chat m = new Chat();
        new T1(m);
        System.out.println("main thread");
        new T2(m);
    }

}

查看运行结果:

main thread
Hi!I am T1
Hi!I am T2
How are you?
I am good, What about you?
I am also doing fine!
Great!

接下来,我们一步一步分析,为什么会出现以上打印结果,看看与我们预期的是否一致。

  1. 首先,实例化一个chat对象,如下:
Chat m = new Chat();
  1. 接着实例化T1,将chat作为参数,传入构造函数.
new T1(m);
  1. 在T1的构造函数里,我们看到,会将m对象赋值给t1的Chat属性,紧接着,创建一个线程,然后执行start()方法[注意,此时有可能子线程start()方法还没开始执行,也即子线程没获取到CPU的时间片,函数会继续往下执行,去实例化T2,但是不管如何,都不影响主流程],在子线程执行start()后,函数会进入到run()方法里,如下
@Override
public void run() {
    for (int i = 0; i < s1.length; i++) {
        m.question(s1[i]);
    }
}
  1. 函数会执行m.question("Hi"),进而进入到Chat类的question(String msg)方法里,如下:
public synchronized void question(String msg) {
    if (flag) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println(msg);
    flag = true;
    notify();
}
  1. 此时,T1子线程获取到m的对象锁,判断flag,初始时,question()方法直接进行打印及以后操作:
// 此时会打印第一次Hi!(也即t1.s1[0])
System.out.println(msg);
flag = true;
notify();
  1. 紧接着,将flag置为true,根据上边的描述T2可能已经被实例化了,此时t2执行answer方法,如下:
public synchronized void answer(String msg) {
    if (!flag) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println(msg);
    flag = false;
    notify();
}
  1. 由于t1在第一次for循环执行question()后占用了对象m,此时还是占用锁,所以即使t2进入了for循环,也获取不到m对象的锁,只能继续被阻塞,此时t1又获得CPU时间分片,继续执行第二次for循环。
    在t1的第一次for循环中,已经将flag设置为true,所以第二次进入的时候,会进去执行wait()操作,从而将自身挂起,并且释放刚才锁占用的对象锁。

  2. t2线程再次获取到CPU的时间分片,然后执行第一次for循环,由于此时flag为true,所以t2会执行如下打印语句,并且将flag设置为false,同时执行notify,t1被唤醒:

// 此时会打印Hi! I am T2, 也即t2.s2[0]
System.out.println(msg);
flag = false;
notify();
  1. 此时,t2还未释放掉m的对象锁,然后进入t2的第二次循环,此时flag=false, t2会执行wait()操作,将自己挂起,同时释放掉m的对象锁,此时t1会获得CPU的时间分片,会继续沿着上次的断点执行,也即打印
System.out.println(t1.s1[1]) -> How are you?
  1. 然后执行flag = true,然后执行notify()操作,同步唤醒t2,然后在t1进行第三次for循环后,又将自己wait(),同时释放自身锁。

  2. 紧接着t2获取到CPU的时间分片,接着上次挂起的断点执行,t2.answer执行打印操作,打印t2.s2[1] -> I am good, What about you?,然后将flag设置为false,并且执行notify()

  3. t2在第三次循环后,由于flag为false,则会执行wait()操作,将自己挂起,并且释放掉m的对象锁,

  4. t1在被分到CPU时间分片后,接着上次的断点执行,打印t1.s1[2] -> I am also doing fine!,同时将flag=true,然后notify(),t2被唤醒,至此,t1执行完毕,生命终止

  5. t2在获得CPU时间分片后,接着上次的断点执行,答应t2.s2[2] -> Great!,至此,t2的生命终止。程序执行完毕!

你可能感兴趣的:(Java 多线程通信)