线程间的通信

news/2024/7/9 3:56:55

线程间的通信使单个独立的线程得到有效组织,提升了CPU利用率。

1、等待/通知机制

     wait方法是使当前执行代码的线程进入等待状态,是Object类的方法,作用是将当前线程置入预执行队列并且在wait方法所在的代码行处停止执行,直至接到通知或被中断为止。在调用wait方法时,线程必须获得该对象的对象级别锁。在执行wait方法之后,当前线程释放锁。在线程重新获得锁之后,会在wait方法后继续执行,直至退出同步区域。wait(long)是在long的时间段内是否有线程进行唤醒,如果没有,超过这个时间之后自动唤醒。

     notify方法需要在同步区域中调用,在调用时必须获得该对象的对象级别锁,所用是通知哪些等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈现wait状态的线程,对其发出notify,使它获得该对象的对象锁。在执行notify之后,线程不会马上释放锁,wait状态的线程也不能马上获得对象锁,需要等到notify方法的线程推出同步区域后,当前线程才会释放锁,wait状态的线程才可以获得对象锁。       当线程呈现wait状态,调用线程对象的interrupt方法会抛出InterruptedException异常。

     notify方法一次只随机通知一个线程进行唤醒。唤醒所有线程可以使用notifyAll方法。

     synchronized关键字提供了一种排他式的数据同步机制,这种同步机制有两个缺陷:1、无法控制阻塞时长;2、阻塞不可被中断。

     注意: 等待/通知机制是需要多个线程相互配合的。如果通知过早,会打乱程序的正常运行逻辑。另外,当wait的条件发生变化,也容易造成程序的逻辑混乱。

     生产者/消费者模式是基于wait/notify原理的线程间通信模型。

     多生产者对多消费者模型示例代码:

public class Resource {
private static int resource=0;
private static int top=10;
private static int low=0;
public static final Object rLock=new Object();

public static int getResource() {
return resource;
}

public static void setResource(int resource) {
Resource.resource = resource;
}

public static int getLow() {
return low;
}

public static void setLow(int low) {
Resource.low = low;
}

public static int getTop() {
return top;
}

public static void setTop(int top) {
Resource.top = top;
}
}
public class Producer implements Runnable{
@Override
public void run() {
synchronized(Resource.rLock) {
if(Resource.getResource()>=Resource.getTop()) {
System.out.println(Thread.currentThread().getName()+" producer thread is waiting");
try {
Resource.rLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(Resource.getResource()<Resource.getTop()) {
Resource.setResource(Resource.getResource()+1);
System.out.println(Thread.currentThread().getName()+" producer thread is working");
Resource.rLock.notifyAll();
}
}
}

}
public class Consumer implements Runnable{

@Override
public void run() {
synchronized(Resource.rLock) {
if(Resource.getResource()<=Resource.getLow()) {
System.out.println(Thread.currentThread().getName()+" comsumer thread is no resource to buy, so notify all thread");
try {
Resource.rLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(Resource.getResource()>Resource.getLow()) {
Resource.setResource(Resource.getResource()-1);
System.out.println(Thread.currentThread().getName()+" comsumer thread is buying");
Resource.rLock.notifyAll();
}
}
}

}
import java.util.ArrayList;

public class CPTest {

public static void main(String[] args) {
Consumer c=new Consumer();
Producer p=new Producer();
ArrayList<Thread> list =new ArrayList<Thread>();
for(int i=0;i<30;i++) {
list.add(new Thread(p));
list.add(new Thread(c));
}
for(int i=0;i<list.size();i++) {
list.get(i).start();
}
}

}

代码编写经验:

1、对象锁最好是常量对象

2、共享资源尽量是静态的

3、synchronized同步区域内代码要写处理wait部分,然后再处理正常部分。这样重新获得锁后会做业务上的正常处理

4、读线程和写线程混杂在一起,notifyAll的效率不高

5、如果唤醒使用notify方法,则在同步区域中,临界条件判断将if替换为while

2、join的使用

    join方法的作用是使所属的线程对象正常执行run方法中的任务,而使当前线程进行无限期阻塞,等join方法所属线程销毁后,再执行当前线程后面的代码。join方法具有使线程排队运行的作用,类似于同步的运行效果。在join过程中,如果当前线程对象被中断,则当前线程出现异常。join(long)的功能是在内部使用wait(long)方法实现的,当前线程释放锁,其他线程可以获得锁执行同步任务,具有释放锁的特点。sleep(long)方法是不释放锁的。由于join方法的机制是当前线程先释放锁,join所属线程再竞争锁,当join所属线程的同步任务与其他线程存在竞争锁的时候,有可能会发生意外情况。

    示例代码:

public class JoinThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName()+" is running");
}

public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" is beginning");
Thread jh=new JoinThread();
jh.start();
try {
jh.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is ending");
}


}

3、ThreadLocal和InheritableThreadLocal的使用

    变量值的共享可以使用public static变量形式,所有线程都使用同一个public static变量。ThreadLocal主要解决每个线程使用的私有数据。

     InheritableThreadLocal可以让子线程中取得父线程继承下来的值。InheritableThreadLocal类保存的值不具备可见性。

     由此可知,ThreadLocal是在每一线程初始化时设定的。 InheritableThreadLocal是在设计父子线程时设定的。

      示例代码:

      在多生产者/多消费者模型的Resource类中加入以下属性

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

      在生产者和消费者的同步区域内设置tl的值,然后使用。当线程全部运行结束时移除。


http://lihuaxi.xjx100.cn/news/1896999.html

相关文章

《Linux C编程实战》笔记:一些系统调用

目录 dup和dup2函数 fcntl函数 示例程序1 示例程序2 ioctl函数 dup和dup2函数 #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd): dup 函数复制 oldfd 参数所指向的文件描述符。 参数&#xff1a; oldfd&#xff1a;要复制的文件描述符的…

selenium 与 chromedriver安装

本文章向大家介绍selenium 安装与 chromedriver安装&#xff0c;主要包括selenium 安装与 chromedriver安装使用实例、应用技巧、基本知识点总结和需要注意事项供大家参考。 一、安装selenium 1、Selenium简介 Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开…

[Verilog] 设计方法和设计流程

主页&#xff1a; 元存储博客 文章目录 1. 设计方法2. 设计流程 3 Vivado软件设计流程总结 1. 设计方法 Verilog 的设计多采用自上而下的设计方法&#xff08;top-down&#xff09;。设计流程是指从一个项目开始从项目需求分析&#xff0c;架构设计&#xff0c;功能验证&#…

【Unity】简单实现生成式电子围栏

【Unity】简单实现生成式电子围栏 三维电子围栏是一种通过使用三维技术和电子设备来建立虚拟围栏&#xff0c;用于监控和控制特定区域的系统。它可以通过使用传感器和摄像头来检测任何越界行为&#xff0c;并及时发出警报。这种技术可以应用于安防领域以及其他需要对特定区域进…

算法--数据结构基础

文章目录 数据结构单链表栈表达式求值前缀表达式中缀表达式后缀表达式 队列单调栈单调队列KMPTrie并查集堆哈希表字符串哈希 数据结构 单链表 用数组模拟&#xff08;静态链表&#xff09;效率比定义Node类&#xff08;动态链表&#xff09;效率高些 使用数组模拟单链表&am…

Redis List类型

列表类型是用来存储多个有序的字符串&#xff0c;如图所示&#xff0c;a、b、c、d、e 五个元素从左到右组成了一个有序的列表&#xff0c;列表中的每个字符串称为元素 (element)&#xff0c;一个列表最多可以存储2的32次方 -1个元素。在 Redis 中&#xff0c;可以对列表两端插入…

CentOS7隐藏版本号防止被扫出openssh漏洞

1、查看当前SSH版本号 strings /usr/sbin/sshd |grep OpenSSH2、将版本号改为hello world sed -i s/OpenSSH_9.0/hello world/g /usr/sbin/sshd3、连接SSH查看版本号

Unity 射线检测(Raycast)检测图层(LayerMask)的设置

目录 主要内容 拓展&#xff1a; 主要内容 Raycast函数有很多重载(函数的重载根据函数的参数来决定) 这里只涉及这个重载,其余重载可以很方便得在Visual Studio中看源码获取&#xff1b; public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hit…