ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java多线程5:线程间的通信

2021-12-21 16:33:10  阅读:129  来源: 互联网

标签:java Thread get System Value 线程 sleep 多线程


在多线程系统中,彼此之间的通信协作非常重要,下面来聊聊线程间通信的几种方式。

wait/notify

想像一个场景,A、B两个线程操作一个共享List对象,A对List进行add操作,B线程等待List的size=500时就打印记录日志,这要怎么处理呢?

一个办法就是,B线程while (true) { if(List.size == 500) {打印日志} },这样两个线程之间就有了通信,B线程不断通过轮训来检测 List.size == 500 这个条件。

这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。

这要非常浪费CPU资源,所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。

定义两个线程类:

public class MyThread1_1 extends Thread {

    private Object lock;

    public MyThread1_1(Object lock) {
        this.lock = lock;
    }

    public void run() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "开始------wait time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println(Thread.currentThread().getName() + "开始------sleep time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束------sleep time = " + System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName() + "结束------wait time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MyThread1_2 extends Thread {

    private Object lock;

    public MyThread1_2(Object lock) {
        this.lock = lock;
    }

    public void run() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "开始------notify time = " + System.currentTimeMillis());
                lock.notify();
                System.out.println(Thread.currentThread().getName() + "开始------sleep time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束------sleep time = " + System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName() + "结束------notify time = " + System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试方法,myThread1先执行,然后sleep 一秒后,myThread2再执行

@Test
    public void test1() throws InterruptedException {
        Object object = new Object();
        MyThread1_1 myThread1_1 = new MyThread1_1(object);
        MyThread1_2 myThread1_2 = new MyThread1_2(object);
        myThread1_1.start();
        Thread.sleep(1000);
        myThread1_2.start();

        myThread1_1.join();
        myThread1_2.join();

    }

执行结果:

Thread-0开始------wait time = 1639464183921
Thread-1开始------notify time = 1639464184925
Thread-1开始------sleep time = 1639464184925
Thread-1结束------sleep time = 1639464186928
Thread-1结束------notify time = 1639464186928
Thread-0开始------sleep time = 1639464186928
Thread-0结束------sleep time = 1639464188931
Thread-0结束------wait time = 1639464188931

可以看到第一行和第二行 开始执行之间只间隔了1s,说明wait方法确实进入等待,

而且没有继续执行wait后面的sleep 2秒,而是执行了notify方法,说明wait方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

第二行和第五行间隔2秒钟,说明notify方法不会释放共享资源的锁。

第6行 说明notify执行完后,唤醒了刚才wait的线程,从而继续执行后面的sleep方法。

说明notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。

另外还有notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。

此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。

方法join

前面的测试方法中几乎都使用了join方法,那么这个方法到底起到什么作用呢?

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,

所以在主线程中使用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;
            }
        }
    }

看下jdk API的源码可以看到,其实join内部使用的还是wait方法进行等待,

join(long millis)方法的一个重点是要区分出和sleep(long millis)方法的区别:

sleep(long millis)不释放锁,join(long millis)释放锁,因为join方法内部使用的是wait(),因此会释放锁。join()其实就是join(0)而已。

ThreadLocal类

ThreadLocal不是用来解决共享对象的多线程访问问题的,而是实现每一个线程都维护自己的共享变量,起到线程隔离的作用。

关于ThreadLocal源码分析可以参考这篇文章: Java多线程9:ThreadLocal源码剖析 - 五月的仓颉 - 博客园 。

下面看个ThreadLocal的例子:

public class Tools {

    public static ThreadLocal<Object> tl = new ThreadLocal<Object>();

}

两个线程类,分别向ThreadLocal里设置值

public class MyThread1_1 extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                Tools.tl.set("ThreadA" + (i + 1));
                System.out.println("ThreadA get Value=" + Tools.tl.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThread1_2 extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                Tools.tl.set("ThreadB" + (i + 1));
                System.out.println("ThreadB get Value=" + Tools.tl.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

@Test
    public void test1() {
        try {
            MyThread1_1 a = new MyThread1_1();
            MyThread1_2 b = new MyThread1_2();
            a.start();
            b.start();
            a.join();
            b.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

执行结果:

ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
ThreadA get Value=ThreadA2
ThreadB get Value=ThreadB2
ThreadA get Value=ThreadA3
ThreadB get Value=ThreadB3
ThreadA get Value=ThreadA4
ThreadB get Value=ThreadB4
ThreadB get Value=ThreadB5
ThreadA get Value=ThreadA5
ThreadB get Value=ThreadB6
ThreadA get Value=ThreadA6
ThreadB get Value=ThreadB7
ThreadA get Value=ThreadA7
ThreadB get Value=ThreadB8
ThreadA get Value=ThreadA8
ThreadA get Value=ThreadA9
ThreadB get Value=ThreadB9
ThreadB get Value=ThreadB10
ThreadA get Value=ThreadA10

可以看到两个线程取出的值没有重复也没有互相影响,其实它内部变化的只是线程本身的 ThreadLocalMap。

感兴趣的还可以去看看 InheritableThreadLocal,它可以在子线程中取得父线程继承下来的值。

参考文献

1:《Java并发编程的艺术》

2:《Java多线程编程核心技术》

标签:java,Thread,get,System,Value,线程,sleep,多线程
来源: https://blog.csdn.net/javamaxiaojing/article/details/122066891

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有