并发编程——线程间通信

线程间通信

1. 单线程间通信

wait和notify

wait和notify方法并不是Thread特有的方法,而是Object中的方法,也就是JDK中的每一个类到拥有这两个方法。

wait
public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException
  1. wait方法的两个重载方法都将用调用native wait(long timeout)方法,wait()方法等价于wait(0),0代表永不超时。
  2. Object的wait(long timeout)方法会导致当前线程进入阻塞,直到有其他线程调用了Object的notify()或者notifyAll方法才能将其唤醒,或者阻塞到达了timeout时间自动唤醒。
  3. wait方法必须拥有该对象的monitor,也就是wait方法必须在同步方法中调用。
  4. 当前线程执行了该对象的wait方法之后,将会放弃对monitor的所有权并且进入与该对象关联的wait set中,也就是说一旦线程执行了某个object的wait方法之后,他就会释放对对该对象monitor的所有权,其他线程也就有机会继续争抢该monitor的所有权。
notify
 public final native void notify();
  1. 唤醒单个正在执行该对象wait方法的线程。
  2. 如果有某个线程由于执行该对象的wait方法而进入阻塞则会被唤醒,如果没有则会忽略。
  3. 被唤醒的线程需要重新获取对该对象所关联monitor的lock才能继续执行。
  4. 如果有任何线程正在等待该对象,则选择其中一个被唤醒。该选择是任意的,并由实施自行决定。

注意事项:

  1. wait方法是可中断方法,当前线程一旦调用了wait方法进入阻塞状态,其他线程是可以使用interrupt方法将其打断的。可中断方法被打断后悔收到中断异常InterruptedException,同时interrupt标识也会被擦除。

  2. 线程执行了某个对象的wait方法以后,会加入与之对应的wait set中,每一个对象的monitor都有一个与之关联的wait set。

  3. 当线程进入wait set之后,notify方法可以将其唤醒,也就是从wait set中弹出,同时中断wait中的线程也会将其唤醒。

  4. 必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是必须持有同步方法的monitor的所有权,运行下面任何一个方法都会抛出非法的monitor状态异常IllgalMonitorStateException:

    private void waitTest() {
    try {
    this.wait();
    } catch(InterruptedException){
    e.printStackTrace();
    }
    }
    private void notifyTest(){
    this.notify();
    }

  5. 同步代码的monitor必须与执行wait notify方法的对象一致,简单来说就是用那个对象的monitor进行同步,就只能用哪个对象进行wait和notify操作。

    public class WaitNotifyTest {
    private final Object MUTEX = new Object();
    private synchronized void testWait() {
    try {
    MUTEX.wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    private synchronized void testNotify() {
    MUTEX.notify();
    }
    }

以上方法运行之后会报

Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.spring.zcl.study.springbootstudy.thread.thread10.WaitNotifyTest.testWait1(WaitNotifyTest.java:29)
at com.spring.zcl.study.springbootstudy.thread.thread10.WaitNotifyTest.main(WaitNotifyTest.java:41)

修改如下即可:

private void testWait() {
    synchronized(MUTEX) {
        try {
            MUTEX.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
private void testNotify() {
    synchronized (MUTEX){
        MUTEX.notify();
    }
}

原因:上述方法中synchronized在方法上,那么monitor引用的是this,而wait和notify方法使用的却是MUTEX的方法,虽然是在同步方法中执行了wait和notify,但是并未以获取MUTEX的monitor为前提。

wait和sleep
  1. wait和sleep方法都可以是线程进入阻塞状态。
  2. wait和sleep方法均是可中断方法,被中断后都会收到中断异常。
  3. wait是Object方法,sleep是Thread特有的方法。
  4. wait方法的执行必须在同步方法中进行,而sleep则不需要。
  5. 线程在同步方法中执行sleep方法时,并不会释放monitor的锁,而wait方法则会释放monitor的锁。
  6. sleep方法短暂休眠之后会主动退出阻塞,而wait方法(没有指定wait时间)则需要被其他线程中断后才能退出阻塞。

2. 多线程间通信

线程休息室 wait set

在虚拟机规范中存在一个wait set(wait set 又被称为线程休息室)的概念,至于该wait set是怎样的数据结构,JDK官方并没有给出明确的定义,不同厂家的JDK有着不同的实现方式,甚至相同的JDK厂家不同的版本也存在着差异,但是不管怎样,线程调用了某个对象的wait方法之后都会被加入与该对象monitor关联的wait set中,并且释放monitor的所有权。

若干个线程调用wait方法之后被加入与monitor关联的wait set中,待另外一个线程调用该monitor的notify方法之后,其中一个线程会从wait set中弹出,至于随机还是先进先出弹出,虚拟机没有强制要求。

notify更不需要考虑哪个线程会被弹出,以为wait set中的所有wait线程将会被弹出。

synchronized缺陷

  1. 无法控制阻塞时长
  2. synchronized同步的线程不可被中断

改造自定义代码:

package com.spring.zcl.study.springbootstudy.thread.Lock;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
 * @Author: zcl
 * @Date: 2022-01-10 14:52
 */
public interface Lock {
    /**
     * this is synchronized use wait and notify implements
     * @throws InterruptedException 中断异常
     */
    void lock() throws InterruptedException;
    /**
     * this is synchronized use wait and notify implements
     * contains time control
     * @param mills time mills
     * @throws InterruptedException 中断异常
     * @throws TimeoutException 超时异常
     */
    void lock(long mills) throws InterruptedException, TimeoutException;
    /**
     * 解锁
     */
    void unlock();
    /**
     * 获取当前阻塞线程
     * @return 阻塞线程集合
     */
    List<Thread> getBlockedThreads();
}

实现:

package com.spring.zcl.study.springbootstudy.thread.Lock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
/**
 * @Author: zcl
 * @Date: 2022-01-10 14:54
 */
public class BooleanLock implements Lock{
    private Thread currentThread;
    private boolean locked = false;
    private final List<Thread> blockedList = new ArrayList<>();
    @Override
    public void lock() throws InterruptedException {
        synchronized (this) {
            while (locked) {
                blockedList.add(Thread.currentThread());
                this.wait();
            }
            blockedList.remove(Thread.currentThread());
            this.locked = true;
            this.currentThread = Thread.currentThread();
        }
    }
    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (mills <= 0) {
                this.lock();
            } else {
                long remainingMills = mills;
                long endMills = System.currentTimeMillis() + remainingMills;
                while (locked) {
                    if (remainingMills <= 0) {
                        throw new TimeoutException("can not get the lock during " + mills);
                    }
                    if (!blockedList.contains(Thread.currentThread())) {
                        blockedList.add(Thread.currentThread());
                    }
                    this.wait(remainingMills);
                    remainingMills = endMills - System.currentTimeMillis();
                }
                blockedList.remove(Thread.currentThread());
                this.locked = true;
                this.currentThread = Thread.currentThread();
            }
        }
    }
    @Override
    public void unlock() {
        synchronized (this) {
            if (currentThread == Thread.currentThread()) {
                this.locked = false;
                Optional.of(Thread.currentThread().getName() + " release the lock.")
                        .ifPresent(System.out::println);
                this.notifyAll();
            }
        }
    }
    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }
}

测试类:

package com.spring.zcl.study.springbootstudy.thread.Lock;
import java.sql.Time;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
 * @Author: zcl
 * @Date: 2022-01-10 15:09
 */
public class RunLock {
    private final Lock lock = new BooleanLock();
        try {
            lock.lock();
            int randomInt = ThreadLocalRandom.current().nextInt(10);
            System.out.println(Thread.currentThread() + " get the lock");
            TimeUnit.SECONDS.sleep(randomInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        test2();
    }
    public static void test1(){
        RunLock runLock = new RunLock();
        IntStream.range(0, 10).mapToObj(i -> new Thread(runLock::syncMethod)).forEach(Thread::start);
    }
    public static void test2() throws InterruptedException {
        RunLock runLock = new RunLock();
        new Thread(runLock::syncMethod, "Thread1").start();
        TimeUnit.SECONDS.sleep(1);
        Thread thread = new Thread(runLock::syncMethod, "Thread2");
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        thread.interrupt();
    }
}

以上代码仍存在问题,就是某个线程被中断,那么它将有可能还处于blockList中,该问题修复如下:

synchronized (this) {
        while (locked) {
            final Thread tempThread = Thread.currentThread();
            try {
                if (!blockedList.contains(tempThread)) {
                    blockedList.add(Thread.currentThread());
                }
                this.wait();
            } catch (InterruptedException e) {
                blockedList.remove(Thread.currentThread());
                throw e;
            }
        }
        blockedList.remove(Thread.currentThread());
        this.locked = true;
        this.currentThread = Thread.currentThread();
    }


public void lock(long mills) throws InterruptedException, TimeoutException {
    synchronized (this) {
        if (mills <= 0) {
            this.lock();
        } else {
            long remainingMills = mills;
            long endMills = System.currentTimeMillis() + remainingMills;
            while (locked) {
                if (remainingMills <= 0) {
                    throw new TimeoutException("can not get the lock during " + mills);
                }
                final Thread tempThread = Thread.currentThread();
                try {
                    if (!blockedList.contains(tempThread)) {
                        blockedList.add(tempThread);
                    }
                    this.wait(remainingMills);
                } catch (InterruptedException e) {
                    blockedList.remove(tempThread);
                    throw e;
                }
                remainingMills = endMills - System.currentTimeMillis();
            }
            blockedList.remove(Thread.currentThread());
            this.locked = true;
            this.currentThread = Thread.currentThread();
        }
    }
}

学习自:
《Java高并发编程详解——多线程与架构设计》 汪文君

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值