Java 多线程通信

并发编程,一般指多线程编程,它可以充分利用计算机的计算资源,使得一个任务可以分为几个子模块同时执行,提高程序执行速度。然而,并不是启动越多线程,就能让程序执行越快,多线程同时带来了上下文切换、多线程间的通信与同步、死锁等问题。合理的利用多线程进行编程是一件有挑战性的事。

Java 自一开始就内置了对多线程的支持,在 JDK1.5 版本中引入了 java.util.concurrent 包,让 Java 的并发编程有了更多的选择和更好的工作方式。


多线程带来的问题

看下面一个例子,猜一下它的输出结果:

public class MultiThread {
    static int i = 0;
    public static int get() {
        return i++;
    }

    public static void main(String[] args) {
        for (int t = 0; t < 10; t++) {
            new Thread() {
                public void run() {
                    System.out.println(get());
                }
            }.start();
        }
    }
}

输出结果:

1
0
2
3
4
5
6
7
8
9

注意,输出结果是不确定的,虽然大部分输出情况都是0-9顺序输出,但总会出现“异常情况”,也就是发生了冲突,当线程越多冲突的机会就越大。

输出结果不确定的原因在于程序中开了10个线程,而这10个线程是同步执行的,可能第二个线程比第一个线程先进行输出,也就出现了上面的结果。至于为什么大部分情况都是按顺序输出,因为虽然这10个线程是同步执行的,但是它们的启动顺序不一样,第一个线程最先启动,最后一个线程最后启动。


让线程按顺序输出

如果现在有10个线程,编号为0-9,我们想让0号线程执行完再执行1号线程,以此类推,最后执行9号线程。解决的方法是使用线程的 join() 方法。

假设线程的名字叫做 t,t.join() 方法的作用是将当前线程加入 t 线程,当 t 线程执行完后再执行当前线程。

示例如下:

public class JoinDemo {
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(String.valueOf(i)) {
                public void run() {
                    String name = Thread.currentThread().getName();
                    int number = Integer.parseInt(name);
                    if (number > 0)
                        try {
                            threads[number - 1].join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    System.out.println("线程名:" + name);
                }
            };
            threads[i].start();
        }
    }
}

输出结果:

线程名:0
线程名:1
线程名:2
线程名:3
线程名:4
线程名:5
线程名:6
线程名:7
线程名:8
线程名:9

程序中我们让后一个线程加到前一个线程中,使得只有前一个线程执行完了才能执行后一个线程。

t.join() 方法内部实际上是调用了 Object的wait() 方法,因此使用 join() 方法之前,需要先获得当前对象的锁,执行 wait() 方法后会释放当前对象的锁。


等待通知机制

等待/通知机制,是指一个线程A调用了对象 O 的 wait() 方法进入等待状态,另一个线程B调用了对象 O 的 notify() 或 notifyAll() 方法通知在对象上等待的线程,使其从 wait() 状态返回。两个线程间通过对象 O 来交互。

notify() 与 notifyAll() 的区别在于前者只会通知一个在对象上等待的线程,如果在该对象上等待的线程有多个,则会随机选取一个;后者会通知所有等待在该对象中的线程。

注意,对于 notifyAll() 来说,虽然所有的线程都被通知了。但是这些线程会进行竞争,且只会有一个线程成功获取到锁,在这个线程执行完毕之前,其他的线程必须等待。但 notifyAll() 免去了线程运行完后再通知其他线程的必要,因为之前已经通知过了所有的线程。

示例如下:

public class WaitNotify {
    static Object lock = new Object();

    static class Wait implements Runnable {
        public void run() {
            synchronized (lock) {
                System.out.println("线程" + Thread.currentThread().getName() + "开始等待");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("继续执行时间:" + LocalTime.now().withNano(0));
            }
        }
    }

    static class Notify implements Runnable {
        public void run() {
            synchronized (lock) {
                System.out.println("线程" + Thread.currentThread().getName() + "开始通知");
                lock.notify();
                System.out.println("通知时间:" + LocalTime.now().withNano(0));
            }
        }
    }

    public static void main(String[] args) {
        Thread wait = new Thread(new Wait(), "wait");
        Thread notify = new Thread(new Notify(), "notify");
        wait.start();
        notify.start();
    }
}

输出结果:

线程wait开始等待
线程notify开始通知
通知时间:12:20:45
继续执行时间:12:20:45

注意,执行 notify() 或 notifyAll() 方法时不会释放 lock 的锁,直到 notify 线程释放了 lock 后,wait 线程才能从 wait() 方法中返回。

猜你喜欢

转载自blog.csdn.net/weixin_43320847/article/details/83045301