Java并发编程-4.多线程之间通讯

什么是多线程之间通讯

  • 多线程之间通讯是指多个线程在操作同一个资源,但是操作的动作不同。
    在这里插入图片描述

代码基本实现

共享资源实体对象

class Res9 {
    public String name;
    public String sex;
}

生产者-写入数据

class ProduceThread extends Thread {
    private Res9 res9;

    public ProduceThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            if (count == 0) {
                res9.name = "小红";
                res9.sex = "女";
            } else {
                res9.name = "小明";
                res9.sex = "男";
            }
            count = (count + 1) % 2;
        }
    }
}

消费者-消费数据

class ConsumeThread extends Thread {
    private Res9 res9;

    public ConsumeThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(res9.name + "----" + res9.sex);
        }
    }
}

运行代码

public class DemoThread9 {

    public static void main(String[] args) {
        Res9 res9 = new Res9();
        new ProduceThread(res9).start();
        new ConsumeThread(res9).start();
    }
}

结果样例

小红----男
小红----男
小红----男
小明----男
小红----女
小明----女
小红----男
小红----

由上面的运行结果,可以看出输出结果的数据发生错误,造成了线程安全问题。

解决线程安全问题

synchronized

生产者-写入数据

class ProduceThread extends Thread {
    private Res9 res9;

    public ProduceThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            if (count == 0) {
                res9.name = "小红";
                res9.sex = "女";
            } else {
                res9.name = "小明";
                res9.sex = "男";
            }
            count = (count + 1) % 2;
        }
    }
}

消费者-消费数据

class ConsumeThread extends Thread {
    private Res9 res9;

    public ConsumeThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res9){
                System.out.println(res9.name + "----" + res9.sex);
            }
        }
    }
}

运行代码

public class DemoThread9 {

    public static void main(String[] args) {
        Res9 res9 = new Res9();
        new ProduceThread(res9).start();
        new ConsumeThread(res9).start();
    }
}

结果样例

小红----女
小红----女
小红----女
小红----女
小红----女
小红----女
小明----

由上面的结果可以看出,数据输出正常,解决了线程安全问题,但是并不是生产者写入一个数据后,消费者消费一个数据,而是消费者和生产者共同抢夺锁资源,谁抢到谁执行
保证生产者执行完成后,消费者在执行,然后生产者执行,这就是线程之间的通讯

wait、notify方法

  • 因为涉及到对象锁,wait、notify一定要放在synchronized中来使用,并且要持有同一把锁
  • wait必须暂停当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
  • notify/notifyall: 唤醒当前对象锁池中的线程,使之运行

共享资源实体对象

class Res9 {
    public String name;
    public String sex;
    //false:表示允许写,不能读
    //true:表示允许读,不能写
    public boolean flag = false;
}

生产者-写入数据

class ProduceThread extends Thread {
    private Res9 res9;

    public ProduceThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (res9) {
                if (res9.flag) {
                    try {
                        //释放当前锁对象,等待被唤醒
                        res9.wait();
                    } catch (InterruptedException e) {
                    }
                }
                if (count == 0) {
                    res9.name = "小红";
                    res9.sex = "女";
                } else {
                    res9.name = "小明";
                    res9.sex = "男";
                }
                res9.flag = true;
                count = (count + 1) % 2;
                //唤醒其他线程
                res9.notify();
            }
        }
    }
}

消费者-消费数据

class ConsumeThread extends Thread {
    private Res9 res9;

    public ConsumeThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res9) {
                if (res9.flag){
                    System.out.println(res9.name + "----" + res9.sex);
                    res9.flag =false;
                    res9.notify();
                }else {
                    try {
                        res9.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    }
}

运行代码

public class DemoThread9 {

    public static void main(String[] args) {
        Res9 res9 = new Res9();
        new ProduceThread(res9).start();
        new ConsumeThread(res9).start();
    }
}

结果样例

小明----男
小红----女
小明----男
小红----女
小明----男
小红----女
小明----男
小红----女
小明----男
小红----

由执行结果可以看出,生产一个数据则读取一个数据,并且线程数据也是安全

wait与sleep区别
  • 对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的
  • sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态
  • 在调用sleep()方法的过程中,线程不会释放对象锁
  • 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

Lock锁

在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

Lock写法

Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.unlock();
}

Lock 接口与 synchronized 关键字的区别

  • Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
  • Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
  • Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回

Condition用法

  • Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能
Condition condition = lock.newCondition();
res. condition.await();   //类似wait
res. Condition.signal()  //类似notify

lock实现上面的案例

生产者-写入数据

class ProduceThread extends Thread {
    private Res9 res9;

    public ProduceThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            if (count == 0) {
                res9.name = "小红";
                res9.sex = "女";
            } else {
                res9.name = "小明";
                res9.sex = "男";
            }
            count = (count + 1) % 2;
        }
    }
}

消费者-消费数据

class ConsumeThread extends Thread {
    private Res9 res9;

    public ConsumeThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res9){
                System.out.println(res9.name + "----" + res9.sex);
            }
        }
    }
}

运行代码

public class DemoThread9 {

    public static void main(String[] args) {
        Res9 res9 = new Res9();
        new ProduceThread(res9).start();
        new ConsumeThread(res9).start();
    }
}

结果样例

小红----女
小红----女
小红----女
小红----女
小红----女
小红----女
小明----

由上面的结果可以看出,数据输出正常,解决了线程安全问题,但是并不是生产者写入一个数据后,消费者消费一个数据,而是消费者和生产者共同抢夺锁资源,谁抢到谁执行
保证生产者执行完成后,消费者在执行,然后生产者执行,这就是线程之间的通讯

wait、notify方法

  • 因为涉及到对象锁,wait、notify一定要放在synchronized中来使用,并且要持有同一把锁
  • wait必须暂停当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
  • notify/notifyall: 唤醒当前对象锁池中的线程,使之运行

共享资源实体对象

class Res9 {
    public String name;
    public String sex;
    //false:表示允许写,不能读
    //true:表示允许读,不能写
    public boolean flag = false;
    //定义lock锁
    public Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
}

生产者-写入数据

class ProduceThread extends Thread {
    private Res9 res9;

    public ProduceThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            try {
                //获取锁的资源
                res9.lock.lock();
                if (res9.flag) {
                    try {
                        res9.condition.await();
                    } catch (InterruptedException e) {
                    }
                }
                if (count == 0) {
                    res9.name = "小红";
                    res9.sex = "女";
                } else {
                    res9.name = "小明";
                    res9.sex = "男";
                }
                res9.flag = true;
                count = (count + 1) % 2;
                res9.condition.signal();
            } catch (Exception ex) {

            } finally {
                //释放锁资源
                res9.lock.unlock();
            }

        }
    }
}

消费者-消费数据

class ConsumeThread extends Thread {
    private Res9 res9;

    public ConsumeThread(Res9 res9) {
        this.res9 = res9;
    }

    @Override
    public void run() {
        while (true) {
            try {
                res9.lock.lock();
                if (!res9.flag) {
                    try {
                        res9.condition.await();
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(res9.name + "----" + res9.sex);
                res9.flag = false;
                res9.condition.signal();
            } catch (Exception ex) {

            } finally {
                res9.lock.unlock();
            }

        }
    }
}

运行代码

public class DemoThread9 {
    public static void main(String[] args) {
        Res9 res9 = new Res9();
        new ProduceThread(res9).start();
        new ConsumeThread(res9).start();
    }
}

结果样例

小明----男
小红----女
小明----男
小红----女
小明----男
小红----
03-19
### IEEE 802.1Q VLAN Tagging Protocol Standard IEEE 802.1Q 是支持虚拟局域网(VLAN)的标准协议之一,通常被称为 Dot1q。该标准定义了一种用于以太网帧的 VLAN 标记系统以及交换机和桥接器处理这些标记帧的操作流程[^2]。 #### 协议结构概述 IEEE 802.1Q 的核心功能在于通过在以太网数据帧中插入特定字段来实现 VLAN 标签的功能。这种标签使得网络设备能够识别哪些流量属于哪个 VLAN,并据此执行转发决策。具体来说: - **Tag Header**: 在原始以太网帧头部增加了一个额外的 4 字节字段作为 VLAN 标签头。这四个字节包含了以下部分: - **Priority Code Point (PCP)**: 使用 3 比特表示优先级级别,范围从 0 到 7,主要用于 QoS 控制。 - **Canonical Format Indicator (CFI)**: 这是一个单比特位,在传统以太网环境中设置为零。 - **VLAN Identifier (VID)**: 使用 12 比特标识具体的 VLAN ID,理论上可以支持多达 4096 个不同的 VLAN(编号从 0 至 4095),其中某些特殊值保留给内部用途或管理目的。 #### 数据包处理机制 当一个带有 VLAN tag 的数据包进入支持 IEEE 802.1Q 的交换机时,它会依据此标签决定如何路由或者过滤该数据流。如果目标端口不属于同一 VLAN,则不会传输至其他无关联的物理接口上;反之亦然——只有相同 VLAN 成员之间才允许互相通信除非经过路由器跨网段访问[^1]。 此外,为了简化管理和配置过程并增强互操作性,还引入了一些辅助性的子协议和服务组件比如 GARP(通用属性注册协议)。GARP 可帮助分发有关 VLAN 成员资格的信息到各个连接节点以便动态调整其行为模式而无需频繁手动干预[^3]。 以下是创建带 VLAN TAG 的 Python 示例代码片段展示如何模拟构建这样的 Ethernet Frame: ```python from scapy.all import Ether, Dot1Q, IP, sendp def create_vlan_packet(src_mac="00:aa:bb:cc:dd:ee", dst_mac="ff:ff:ff:ff:ff:ff", vlan_id=100, src_ip="192.168.1.1", dst_ip="192.168.1.2"): ether = Ether(src=src_mac, dst=dst_mac) dot1q = Dot1Q(vlan=vlan_id) ip_layer = IP(src=src_ip, dst=dst_ip) packet = ether / dot1q / ip_layer return packet packet = create_vlan_packet() sendp(packet, iface="eth0") # Replace 'eth0' with your network interface name. ``` 上述脚本利用 Scapy 库生成包含指定源地址、目的地址及所属 VLAN 编号的数据报文并通过选定的网卡发送出去测试实际效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值