线程间通信的常用方式

1.简介

线程通信简单来说就是实现线程的交替工作,传递信息。例如在一个方法中我有两个线程A和B在运行,我希望线程A先向一个集合里面循环新增数据,当增加到第五次的时候,线程B才开始执行其他的操作。
线程间通信的模型有两种:共享内存消息传递

2.共享内存模型

1)volatile关键字

使用volatile关键字简单来说就是多个线程同时监听一个变量,当该变量发生变化的时候,所有的监听的线程能够感知。

public class TestSyncVolatile {

    // 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知
    static volatile boolean notice = false;

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // 实现线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 4) {
                    notice = true;
                }
            }
        });
        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                    System.out.println("此时list中的元素个数为:" + list.size());
                    break;
                }
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }

}

iOS线程如何通讯 线程间怎么通信_线程

3.消息传递

1)wait()/notify()(notifyAll())方法

wait()/notify()/notifyAll() 方法必须配合 synchronized关键字使用

wait()方法会暂时让出同步锁,以便其他正在等待此锁的线程可以得到锁并运行,其他线程调用了notify()/notifyAll()并不会立刻释放锁(notify()是随机释放单个调用当前锁的wait状态的线程,notifyAll()是释放全部调用当前锁的wait状态的线程),而是先等当前线程方法执行完才会释放锁,之后所有等待此锁的线程可以去参与获得锁的竞争,调用当前锁的 wait() 的一个或多个线程会解除 wait 状态,重新参与竞争对象锁, 得到锁的线程会继续执行。

public class TestSyncWait {

    public static void main(String[] args) {
        //定义一个锁对象
        Object lock = new Object();
        List<String> list = new ArrayList<>();
        // 线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                synchronized (lock) {
                    list.add("abc");
                    System.out.println("线程A添加元素,此时list的size为:" + list.size());
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (list.size() == 5) {
                        lock.notify();
						System.out.println("线程A还没有释放lock的同步锁");
                    }
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
            }
        });
        //需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再启动线程A
        threadA.start();
    }

}

iOS线程如何通讯 线程间怎么通信_java_02

iOS线程如何通讯 线程间怎么通信_iOS线程如何通讯_03


就比如上文的示例中,先启动线程B,线程B获取lock实例的锁,但是集合中没有5个参数,所以调用wait()方法,释放lock实例的锁,此时线程A获取并执行,当集合中参数个数到达5个时,调用lock.notify()释放lock实例的单个锁,但是此时本次循环还没结束,所以继续先执行完本次循环内容之后才释放释放lock实例的单个锁,之后线程A和线程B共同竞争lock锁,有可能线程A继续抢到锁继续循环,也有可能线程B抢到锁执行他的代码,所以每次启动都可能结果不同。

2)ReentrantLock

使用ReentrantLock和wait()/notify()的大体执行逻辑相同,但还是有不少区别:

wait()/notify():
锁调用notify()方法后,会等当前方法执行完才释放锁。
配合 synchronized关键字使用,synchronized为非公平锁,notify()之后所有等待锁的线程会去竞争获取锁。

ReentrantLock:
锁调用unlock方法后会立即释放锁。
公平锁,结合Condition使用,会释放指定的对象的锁。

public class TestSyncReentrantLock {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        List<String> list = new ArrayList<>();
        //线程A
        Thread threadA = new Thread(() -> {
            lock.lock();
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5) {
                    condition.signal();
                    lock.unlock();
                    System.out.println("线程A还没有释放lock的同步锁");
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            lock.lock();
            if (list.size() != 5) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
            lock.unlock();
        });
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadA.start();
    }

}

iOS线程如何通讯 线程间怎么通信_java_04

3)CountDownLatch

CountDownLatch使用方式与ReentrantLock类似,执行完countDown()方法后,不必等当前线程剩下方法执行完,可直接唤醒等待线程,执行其代码。

public class TestSyncCountDownLatch {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        List<String> list = new ArrayList<>();
        //线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5) {
                    countDownLatch.countDown();
                    System.out.println("线程A其他业务代码");
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        //需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再启动线程A
        threadA.start();
    }

}

iOS线程如何通讯 线程间怎么通信_iOS线程如何通讯_05

4)LockSupport

LockSupport 是一种灵活的实现线程间阻塞和唤醒的工具。他可以根据线程名唤醒指定线程,唤醒之后不必等当前方法执行完,被唤醒线程可立刻执行其代码。

public class TestSyncLockSupport {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        //线程B
        final Thread threadB = new Thread(() -> {
            if (list.size() != 5) {
                LockSupport.park();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        //线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5) {
                    LockSupport.unpark(threadB);
                    System.out.println("线程A其他业务代码...");
                }
            }
        });
        threadA.start();
        threadB.start();
    }

}

iOS线程如何通讯 线程间怎么通信_System_06