Java多线程编程核心技术(三)多线程通信(下篇)

简介: 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。

在本章中需要着重掌握的技术点如下:

  • 方法join的使用
  • ThreadLocal类的使

4.方法join的使用

在很多情况下,主线程创建并启动了子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到 join() 方法了。方法 join() 的作用是等待线程对象销毁。

示例代码:

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        thread.join();
        System.out.println("我想在thread执行完之后执行,我做到了");
    }

}

打印结果:

Thread-0执行完毕
我想在thread执行完之后执行,我做到了

方法join() 的作用是使所属的线程对象 x 正常执行 run() 方法中的任务,而使当前线程 z 进行无限期的阻塞,等待线程x 销毁后再继续执行线程z 后面的代码。

join与synchronized的区别是:join 在内部使用 wait() 方法进行等待,而synchronize 关键字使用的是“对象监视器”原理做为同步。

在前面已经讲到:当线程呈 wait() 方法时,调用线程对象的 interrupt() 方法会出现 InterruptedException 异常。说明方法 join() 和 interrupt() 方法如果彼此遇到,则会出现异常。

4.1 方法 join(long) 的使用

方法 join(long) 中的参数是设定等待的时间。

4.2 join(long) 和 sleep(long) 的区别

方法 join(long) 的功能在内部是使用 wait(long) 方法来实现的,所以 join(long) 方法具有释放锁的特点。

方法 join(long) 的源代码如下:

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

从源代码可以了解到,当执行 wait(long) 方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。而 Thread.sleep() 方法却不释放锁。

5.类ThreadLocal的使用

变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个 public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的类ThreadLocal正是为了解决这样的问题。

类ThreadLocal 主要解决的就是每个线程绑定自己的值,可以将 ThreadLocal 类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

示例代码:

public class LocalThread extends Thread {
    private static ThreadLocal local = new ThreadLocal();

    @Override
    public void run() {
        local.set("线程的值");
        System.out.println("thread线程:"+ local.get());
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(local.get());
        local.set("main的值");
        LocalThread t = new LocalThread();
        t.start();
        Thread.sleep(1000);
        System.out.println("main线程:"+ local.get());
    }

}

打印结果:

null
thread线程:线程的值
main线程:main的值

在第一次调用get()方法返回的是null,怎么样能实现第一次调用get()不返回 null 呢?也就是具有默认值的效果。

答案是继承 LocalThread 类重写 initialValue() 方法:

public class Local extends ThreadLocal {

    @Override
    protected Object initialValue() {
        return new Date();
    }

}

ThreadLocal原理

ThreadLocal内部使用了ThreadLocalMap,ThreadLocal的set方法内部通过当前线程对象获取ThreadLocalMap对象,然后将当前ThreadLocal对象作为Key与Value一起保存到ThreadLocalMap中。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal的get方法内部也是通过当前线程对象获取ThreadLocalMap对象,把当前ThreadLocal对象作为Key,获取Value。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

6.类 InheritableThreadLocal 的使用

使用类 InheritableThreadLocal 可以在子线程中取得父线程继承下来的值。

示例代码:

public class LocalThread extends Thread {
    private static InheritableThreadLocal local = new InheritableThreadLocal();

    @Override
    public void run() {
        System.out.println("thread线程:"+ local.get());
    }

    public static void main(String[] args) throws InterruptedException {
        local.set("main的值");
        LocalThread t = new LocalThread();
        t.start();
        System.out.println("main线程:"+ local.get());
    }

}

如果想要自定义 get() 方法默认值,具体操作也和 ThreadLocal 是一样的。

public class Local extends InheritableThreadLocal {

    @Override
    protected Object initialValue() {
        return new Date();
    }
}

InheritableThreadLocal 提供继承的同时还可以进行进一步的处理。代码如下:

public class Local extends InheritableThreadLocal {

    @Override
    protected Object initialValue() {
        return new Date();
    }

    @Override
    protected Object childValue(Object parentValue) {
        return parentValue+"[子线程增强版]";
    }
}

但在使用 InheritableThreadLocal 类需要注意一点的是,如果子线程在取得值的同时,主线程将 InheritableThreadLocal 中的值进行更改,那么子线程取到的值还是旧值。

7.文末总结

经过本文的学习,可以将以前分散的线程对象进行彼此的通信与协作,线程任务不再是单打独斗,更具有团结性,因为它们之间可以相互通信。

嗨,你还在看吗?

文章来源:微信公众号 薛勤的博客

目录
相关文章
|
11天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
15天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
49 12
|
11天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
93 2
|
28天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
28天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
50 3
|
Java
Java多线程编程核心技术(三)多线程通信(上篇)
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。
2565 0
|
Java 安全
Java多线程编程核心技术(二)volatile关键字
关键字volatile的主要作用是使变量在多个线程间可见。
898 0
|
Java
Java多线程编程核心技术(一)Java多线程技能
本文为《Java并发编程系列》第一章,主要介绍并发基础概念与API
2447 0
|
Java
<Java多线程编程核心技术>讲解得太细致啦
一个synchronized关键字,能讲一百多页,搞出几十个小举例。 我是服了!
2242 0
|
存储 Java Apache
java多线程编程核心技术
一,共享资源 使用sleep()观察数据紊乱 注意:以下几份代码其中生产者(Producer.java),消费者(Consumer.java),和测试类(TestDemo.
1054 0