前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程间的协作(线程通信)

线程间的协作(线程通信)

作者头像
leobhao
发布2022-06-28 18:16:34
3960
发布2022-06-28 18:16:34
举报
文章被收录于专栏:涓流

线程的状态

Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。

线程中各种状态的转换关系如下图:

wait/notify/notifyAll

这三个方法都是Object上的方法, 只有获取到了所调用对象的monitor锁才能进行调用。是什么意思呢,举个例子:

代码语言:javascript
复制
public class WaitT {

    public Object lock = new Object();

    public void testWait() {
        synchronized (lock) {
            try {
                lock.wait(1000);
                System.out.println("end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        final WaitT waitT = new WaitT();
        new Thread(() -> {
            waitT.testWait();
        }).start();
    }
}

上述例子中,只有获取到了lock对象的monitor锁(通过synchronize)才能调用lock.wait(注意,这里是通过lock对象才能调用wait,因为是获取的lock对象的锁)

wait

JDK中一共提供了三种wait方法:

  1. wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程.
  2. wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒
  3. 至于wait(long timeout,long nanos),本意在于更精确的控制调度时间

wait方法的使用必须在同步的范围内(获得monitor的锁),否则就会抛出IllegalMonitorStateException异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll 方法的唤醒,或等待超时后自动唤醒。

notify/notifyAll

既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll 的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而n6otifyAll则唤醒所有的线程

sleep/join/yield

这三个方法是Thread上的方法

sleep

sleep方法的作用是让当前线程暂停指定的时间(毫秒),sleep方法是最简单的方法,在上述的例子中也用到过,比较容易理解。唯一需要注意的是其与wait方法的区别。最简单的区别是,wait方法依赖于同步,而sleep方法可以直接调用。

而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则会释放锁。通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的.

这个又怎么理解呢,还是用具体的例子来说明:

代码语言:javascript
复制
public class SleepT {

    Object lock = new Object();

    public void sleepSyn() {
        synchronized (lock) {
            try {
                System.out.println(Thread.currentThread().getName() + " sleep start");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread().getName() + " sleep end");
            } catch (InterruptedException e) {}
        }
    }

    public static void main(String[] args) {
        SleepT sleepT = new SleepT();
        new Thread( () -> sleepT.sleepSyn(), "thread1").start();
        sleepT.sleepSyn();
    }
}

不管是那个线程先进入sleepSyn, 都会把threadName sleep start | threadName sleep end打印完成后,才会让下一个线程访问,也就是说当持有对象锁的时候,sleep期间是不会释放的

join

join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。

join也有三种调用方式:

代码语言:javascript
复制
void join()
void join(long millis)
void join(long millis, int nanos)

join的源码如下:

代码语言:javascript
复制
public final void join() throws InterruptedException {
        join(0);
    }

 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;
            }
        }
    }

怎么去理解join呢,这个问题最开始困扰了我很久。我的理解是:

发起join调用的线程等待join的线程执行完了之后才会执行

有一些绕口,还是用一个例子来理解:

代码语言:javascript
复制
public class JoinT {

    public void print() {
        System.out.println("sun thread run");
    }

    public static void main(String[] args) {
        System.out.println("main thread start");
        JoinT joinT = new JoinT();
        Thread sunThread = new Thread(() -> joinT.print());
        sunThread.start();
        try {
            sunThread.join();
        } catch (InterruptedException e) {}
        System.out.println("main thread end");
    }
}

main thread 会等待 sunThread 执行完了之后在执行。从join的源码可以看到,发起join调用实际上等于获取了sunThread的monitor锁后,调用了sunThread.wait,需要等待sunThread.notify(notifyAll)才能唤醒。但是我们并没有看到唤醒的代码。这是因为当 Thread 退出后(Thread.exit), cpp的源码会自动执行 Thread.notifyAll。所以就能理解,为什么join线程执行完成后,调用join的线程会被唤醒执行

yield

yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态

yield是一种线程让步,暂时让出时间片,但是下一次时间片同样有机会抢占

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程的状态
  • wait/notify/notifyAll
    • wait
      • notify/notifyAll
      • sleep/join/yield
        • sleep
          • join
            • yield
            • 参考资料
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档