二.线程间的通信

每个线程都拥有自己的栈空间,按照既定的代码一步一步地执行,直到终止。如果多个线程能够互相配合完成工作,那么将会带来巨大的价值。下面就来介绍几个线程间通信的方式。

 

变量共享

Java支持多个线程同时访问一个对象或者对象的成员变量,但是由于每个线程可以拥有这个变量的拷贝,所以程序在执行的过程中,一个线程看到的变量不一定是最新的。对于这个问题,Java提供了volatile关键字和synchronized关键字,对于这两个关键字,后面会开一篇文章来详细解释。

volatile

一旦某个变量被这个关键字所修饰,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

synchronized

该关键字可以修饰方法或者以同步块的形式来进行使用,它确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

 

等待/通知机制

一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行的又是另一个线程。前者是生产者,后者是消费者,这种模式隔离了“做什么”和“怎么做”,在功能层面上实现了解耦,体系结构上具备了良好的伸缩性。

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在Object类上,如下表

方法名称描述
nofity()通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll()通知所有等待在该对象上的线程
wait()调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁
wait(long)超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
wait(long,int)对于超时时间更细粒度的控制,可以达到纳秒

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,从而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和nofity/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

查看以下案例


    static boolean flag = true;

    static Object lock = new Object();

    static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    public static void main(String[] args) throws Exception{
        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();
    }

    static class Wait implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                while (flag) {
                    System.out.println(Thread.currentThread() + " flag is true. wait @ "
                            + LocalDateTime.now()
                                           .format(formatter));
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread() + " flag is false. running @ "
                        + LocalDateTime.now()
                                       .format(formatter));
            }
        }
    }

    static class Notify implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread() + " hold lock. notify @ "
                        + LocalDateTime.now()
                                       .format(formatter));
                lock.notifyAll();
                flag = false;
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            synchronized (lock){
                System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
                        + LocalDateTime.now()
                                       .format(formatter));
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

运行结果

Thread[WaitThread,5,main] flag is true. wait @ 12:55:38
Thread[NotifyThread,5,main] hold lock. notify @ 12:55:39
Thread[NotifyThread,5,main] hold lock again. sleep @ 12:55:44
Thread[WaitThread,5,main] flag is false. running @ 12:55:49

有一些注意事项:

1.使用wait()、notify()和notifyAll()时需要先对调用对象加锁

2.调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前贤臣放置到对象的等待队列。

3.notify()或者notify()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或者notify()的线程释放锁之后,等待线程才有机会从wait()返回。

4.notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是等待队列中所有的线程全部转移到同步队列,被移动的线程状态由WAITING变为BLOCKED。

5.从wait()方法返回的前提是获得了调用对象的锁。               

 

等待/通知的经典范式

该范式分为两部分,分别针对等待方(消费者)和通知方(生产者)

等待方遵循如下原则:

1.获取对象的锁

2.如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

3.条件满足则执行对应的逻辑。

synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
    对应的处理逻辑
}

通知方遵循如下原则:

1.获得对象的锁

2.改变条件

3.通知所有等待在对象上的线程

synchronized(对象){
    改变条件
    对象.notifyAll();
}

Thread.join()的使用

如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。

查看下面的案例,创建了10个线程,每个线程都会调用前一个线程的join方法

    public static void main(String[] args) throws Exception{
        Thread previous = Thread.currentThread();
        for (Integer i = 0; i < 10; i++) {
            Thread thread = new Thread(new Domino(previous), i.toString());
            thread.start();
            previous = thread;
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread()
                                 .getName() + " terminate.");
    }

    static class Domino implements Runnable {

        private Thread thread;

        public Domino(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()
                                     .getName() + " terminate.");
        }
    }

结果如下

main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

join方法其实也是用上一节的等待/通知经典范式来实现的,查看源码

    /**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }

从源码可以看出,在A线程中调用B.join()之后,在方法内部,循环判断线程B是否存活,如果还存活,则调用了B.wait(0),如果设置了等待时间,则B.wait(delay),然后看方法注释中有这么一句         

As a thread terminates the {@code this.notifyAll} method is invoked

也就是在线程终止之后,会调用该线程对象自身的notifyAll()方法。与上述的等待/通知经典范式一致                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

 

ThreadLocal的使用

线程变量,一个线程可以根据一个ThreadLocal对象查询到绑定到这个线程上的一个值。

查看下面的案例,可以被复用来计算方法的耗时,begin和end的调用可以不必在一个方法或一个类中。

public class Profiler {

    private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>();

    public static final void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static final long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }

    public static void main(String[] args) throws Exception {
        Profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("cost:" + Profiler.end() + "mills");
    }

}

结果

cost:1004mills

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值