线程会很感谢你的,给它一个可以通信的传话筒

简介: 每个Java线程开始运行,按照你写的逻辑一步步的执行着,就像一个可怜的脚本,孤独地活着!只有它们内部的栈空间、程序计数器在陪伴着它们。大家给可怜的单线程多加几个玩伴吗?创建一个多线程环境,给它们一个传话筒,让它们可以互相通信、互相问好、嬉皮打闹,看来单线程会很感谢你的。

每个Java线程开始运行,按照你写的逻辑一步步的执行着,就像一个可怜的脚本,孤独地活着!只有它们内部的栈空间、程序计数器在陪伴着它们。

大家给可怜的单线程多加几个玩伴吗?创建一个多线程环境,给它们一个传话筒,让它们可以互相通信、互相问好、嬉皮打闹,看来单线程会很感谢你的。

在这里插入图片描述

本文收录在我开源的《Java学习面试指南》中,一份覆盖Java程序员所需掌握的Java核心知识、面试重点。希望收到大家的 ⭐ Star ⭐支持。GitHub地址:https://github.com/hdgaadd/JavaGetOffer,相信你看了一定不会后悔。

1. 线程通信

1.1 线程的等待/通知机制

面试官:Java线程的等待/通知机制知道吧?

Java线程的等待/通知机制指的是:线程A获得了synchronized同步方法、同步方法块的锁资源后,调用了锁对象的wait()方法,释放锁的同时进入等待状态;而线程B获得锁资源后,再通过锁对象的notify()或notifyAll()方法来通知线程A恢复执行逻辑。

其实Java的所有对象都拥有等待/通知机制的本领,大家可以在JDK源码package java.lang`下找到Java.lang.Object里提供的五个与等待/通知机制相关的方法。

一、等待。

(1)使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法。

    public final void wait() throws InterruptedException {
   
   
        wait(0);

(2)使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者指定的毫秒timeout过去。

    public final native void wait(long timeout) throws InterruptedException;

(3)使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者指定的毫秒timeout过去,另外nanos是额外时间,以纳秒为单位。

    public final void wait(long timeout, int nanos) throws InterruptedException {
   
   
    }

所以其实wait()、watit(0)、watit(0, 0)执行后都是同样的效果

二、通知。

(1)唤醒在此对象监视器上等待的单个线程。

    public final native void notify();

(2)唤醒在此对象监视器上等待的所有线程。

    public final native void notifyAll();

大家有没听说过消费者生产者问题呢?消费者生产者之间要无限循环生产和消费物品,解决之道就是两者形成完美的等待、通知机制。而这套机制就可以通过上文的wait、notify方法来实现。

1.2 线程通信方式

面试官:还有没有其他线程通信方式?

(1)利用Condition进行线程通信。

如果大家的程序直接采用的是Lock对象来同步,则没有了上文synchronized锁带来的隐式同步器,也就无法使用wait()、notify()方法。

此时的线程可以使用Condition对象来进行通信。例如下文的示例代码: condition0的await()阻塞当前线程,同时释放、等待获取锁资源;接着等待其他线程调用condition0的signal()来通知其获取锁资源继续执行。

@Slf4j
public class UseReentrantLock {
   
   

    private static final ReentrantLock lock = new ReentrantLock();

    private static final Condition condition0 = lock.newCondition();

    private static final Condition condition1 = lock.newCondition();

    public static void main(String[] args) {
   
   
        new Thread(() -> {
   
   
            try {
   
   
                lock.lock();

                for (int i = 1; i < 4; i++) {
   
   
                    log.info(i + "");

                    condition1.signal();
                    condition0.await();
                }
            } catch (Exception e) {
   
   
                e.printStackTrace();
            } finally {
   
   
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
   
   
            try {
   
   
                lock.lock();

                for (int i = 65; i < 68; i++) {
   
   
                    log.info((char) i + "");

                    condition0.signal();
                    condition1.await();
                }
            } catch (Exception e) {
   
   
                e.printStackTrace();
            } finally {
   
   
                lock.unlock();
            }
        }).start();
    }
}
# 程序执行结果
2024-05-30 10:30:30[ INFO ]1
2024-05-30 10:30:30[ INFO ]A
2024-05-30 10:30:30[ INFO ]2
2024-05-30 10:30:30[ INFO ]B
2024-05-30 10:30:30[ INFO ]3
2024-05-30 10:30:30[ INFO ]C

(2)Thread采用join方法进行通信。

线程Thread对象还提供了join方法,也是一种通信的方式。当某个程序的执行流调用了某个thread对象的join方法,调用线程将会被阻塞,等到thread对象终止后才通知调用线程继续执行。

    public final void join() throws InterruptedException {
   
   
        join(0);
    }

(3)volatile共享变量。

volatile的出现,大家是不是有些意外呢?虽然volatile适用的多线程场景不多,但它也是线程通信的一种方式。被volatile修饰的变量如果更新了值,则会通过主内存这条消息总线通知所有使用该变量的线程,让其把主内存同步到工作内存里,则所有线程都会获取共享变量最新值。

1.3 更加灵活的ReentrantLock

面试官:你说的Lock对象说下你的理解?

在线程同步上,JDK的Lock接口提供了多个实现子类,如下所示。下面我按面试官面试频率高的ReentrantLock来讲解。

在这里插入图片描述

ReentrantLock相比synchronized来说使用锁更加灵活,可以自由进行加锁、释放锁。ReentrantLock类提供了lock()、unlock()来实现以上操作。具体实操代码可以看上一个面试官问题关于Condition的示例代码。

// ReentrantLock源码
package java.util.concurrent.locks;
public class ReentrantLock implements Lock, java.io.Serializable {
   
   

    // 获取锁
    public void lock() {
   
   
        sync.lock();
    }

    // 尝试释放此锁
    public void unlock() {
   
   
        sync.release(1);
    }
}

另外ReentrantLock和synchronized都是可重入锁,即线程获取锁资源后,下一步如果进入相同锁资源的同步代码块,不需要再获取锁。

ReentrantLock也可以实现公平锁,即成功获取锁的顺序与申请锁资源的顺序一致。我们在创建对象时进行初始化设置就可以设置为公平锁。

 ReentrantLock lock = new ReentrantLock(true);

2. ThreadLocal作用

面试官:ThreadLocal知道吧?

上文我们讨论的都是在多个线程对共享资源进行通信的业务场景上,例如商城业务秒杀的库存要保证数据安全性。而如果在多个线程对共享资源进行线程隔离的业务场景上,则可以使用ThreadLoccal来解决。

ThreadLocal可以保存当前线程的副本值,提供了set、get方法,通过set方法可以把指定值设置到当前线程副本;而通过get方法可以返回此当前线程副本中的值。

例如要实现一个功能,每个线程打印当前局部变量:局部变量 + 10,我们就可以利用ThreadLocal保存共享变量i,来避免对变量i的共享冲突。

public class UseThreadLocal {
   
   
    public static void main(String[] args) {
   
   
        ExecutorService es = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 3; i++) {
   
   
            int number = i;
            es.execute(() -> System.out.println(number + ":" + new intUtil().addTen(number)));
        }
    }

    private static class intUtil {
   
   
        public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 使用threadLocal保存线程保存的当前共享变量num

        public static int addTen(int number) {
   
   
            threadLocal.set(number);

            try {
   
    // 休息1秒
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
            }
            return threadLocal.get() + 10;
        }
    }
}
# 程序执行结果
0:10
2:12
1:11

3. 线程生命周期

面试官:那线程生命周期都有什么?

在校招笔试、或面试中,这道面试题还是比较常见的,大家简单记忆下就可以。

  1. 初始状态。创建了线程对象还没有调用start()。

  2. 就绪或运行状态。执行了start()可能运行,也可能进入就绪状态在等待CPU资源。

  3. 阻塞状态 。一直没有获得锁。

  4. 等待状态。等待其他线程的通知唤醒。

  5. 超时状态。

  6. 终止状态。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

相关文章
|
2月前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
41 1
[Java]线程生命周期与线程通信
|
5月前
|
Java
实现Java多线程中的线程间通信
实现Java多线程中的线程间通信
|
1月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
38 3
|
6月前
|
Java 程序员
从菜鸟到大神:JAVA多线程通信的wait()、notify()、notifyAll()之旅
【6月更文挑战第21天】Java多线程核心在于wait(), notify(), notifyAll(),它们用于线程间通信与同步,确保数据一致性。wait()让线程释放锁并等待,notify()唤醒一个等待线程,notifyAll()唤醒所有线程。这些方法在解决生产者-消费者问题等场景中扮演关键角色,是程序员从新手到专家进阶的必经之路。通过学习和实践,每个程序员都能在多线程编程的挑战中成长。
59 6
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
24 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
48 1
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
31 1
|
2月前
|
Java
|
6月前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
112 0
|
2月前
多线程通信和同步的方式有哪些?
【10月更文挑战第6天】
118 0