zoukankan      html  css  js  c++  java
  • 【Java并发】线程通信

    一、概述

    1.1 什么是多线程之间通讯?

    • 多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。

    1.2 案例

    • 需求:第一个线程写入(input)用户,另一个线程取读取(out)用户。实现读一个,写一个操作。

    代码实现

    /**
     * 测试等待/通知机制
     * @author hao
     *
     */
    public class Test_NoWaitNotify {
        public static void main(String[] args) {
            Res res = new Res();
            IntThrad intThrad = new IntThrad(res);
            OutThread outThread = new OutThread(res);
            intThrad.start();
            outThread.start();
        }
    }
    
    /*
     * 共享资源源实体类
     * 
     */
    class Res {
        public String userSex;
        public String userName;
    }
    
    /*
     * 
     * 输入线程资源
     * 
     */
    
    class IntThrad extends Thread {
        private Res res;
    
        public IntThrad(Res res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            int count = 0;
            while (true) {
                if (count == 0) {
                    res.userName = "小明";
                    res.userSex = "男";
                } else {
                    res.userName = "小红";
                    res.userSex = "女";
                }
                count = (count + 1) % 2;
            }
        }
    }
    
    /*
     * 
     * 输出线程
     * 
     */
    
    class OutThread extends Thread {
        private Res res;
    
        public OutThread(Res res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            while (true) {
                System.out.println(res.userName + "--" + res.userSex);
            }
        }
    }
    
    • 改例中数据发生错乱,造成线程安全问题

    解决线程安全问题

    加入synchronized 关键字

    public class Test002 {
        public static void main(String[] args) {
            Res2 res = new Res2();
            IntThrad2 intThrad = new IntThrad2(res);
            OutThread2 outThread = new OutThread2(res);
            intThrad.start();
            outThread.start();
        }
    }
    
    /*
     * 共享资源源实体类
     * 
     */
    class Res2 {
        public String userSex;
        public String userName;
    }
    
    /*
     * 
     * 输入线程资源
     * 
     */
    
    class IntThrad2 extends Thread {
        private Res2 res;
    
        public IntThrad2(Res2 res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    if (count == 0) {
                        res.userName = "小明";
                        res.userSex = "男";
                    } else {
                        res.userName = "小红";
                        res.userSex = "女";
                    }
                    count = (count + 1) % 2;
                }
            }
        }
    }
    
    /*
     * 
     * 输出线程
     * 
     */
    
    class OutThread2 extends Thread {
        private Res2 res;
    
        public OutThread2(Res2 res) {
            this.res = res;
        }
    
        @Override
        public void run() {
    
            while (true) {
                synchronized (res) {
                    System.out.println(res.userName + "--" + res.userSex);
                }
            }
        }
    }
    

    二、等待通知机制

    2.1 示例

    • 上面例子中解决了线程安全问题,但是如果我们要求读一个写一个,那么该如何解决
    • 1.因为涉及到对象锁,他们必须都放在synchronized中来使用. Wait、Notify一定要在synchronized里面进行使用。
    • 2.Wait必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
    • 3.notify/notifyall: 唤醒因锁池中的线程,使之运行
    • 注意:一定要在线程同步中使用,并且是同一个锁的资源
    class Res3 {
        public String userSex;
        public String userName;
        // 线程通讯标识
        public boolean flag = false;
    }
    
    class IntThrad3 extends Thread {
        private Res3 res;
    
        public IntThrad3(Res3 res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    if (res.flag) {
                        try {
                            // 当前线程变为等待,但是可以释放锁
                            res.wait();
                        } catch (Exception e) {
    
                        }
                    }
                    if (count == 0) {
                        res.userName = "小明";
                        res.userSex = "男";
                    } else {
                        res.userName = "小红";
                        res.userSex = "女";
                    }
                    count = (count + 1) % 2;
                    res.flag = true;
                    // 唤醒当前线程
                    res.notify();
                }
    
            }
        }
    }
    
    class OutThread3 extends Thread {
        private Res3 res;
    
        public OutThread3(Res3 res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    if (!res.flag) {
                        try {
                            res.wait();
                        } catch (Exception e) {
                            // TODO: handle exception
                        }
                    }
                    System.out.println(res.userName + "--" + res.userSex);
                    res.flag = false;
                    res.notify();
                }
            }
        }
    }
    
    public class Test003 {
        public static void main(String[] args) {
            Res3 res = new Res3();
            IntThrad3 intThrad = new IntThrad3(res);
            OutThread3 outThread = new OutThread3(res);
            intThrad.start();
            outThread.start();
        }
    }
    

    2.2 wait与sleep区别

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

    三、Lock锁

    3.1 概述

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

    • Lock写法,基本用法

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

    3.2 等待/通知机制(Condition)

    • Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能。

    • 代码示例,将上面将的例子改造如下

    public class TL002_Condition {
        public static void main(String[] args) throws InterruptedException {
    
            Res res = new Res();
            IntThread intThread = new IntThread(res);
            OutThread outThread = new OutThread(res);
            intThread.start();
            outThread.start();
        }
    }
    
    /*
     * 共享资源
     */
    class Res {
    
        public String name;
        public String sex;
        public boolean flag = false;
        public Lock lock = new ReentrantLock();
        public Condition condition=lock.newCondition();
    }
    
    /*
     * 写入线程
     */
    class IntThread extends Thread {
        public Res res;
    
        public IntThread(Res res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            int count = 0; // 1
            while (true) {
                try {
                    res.lock.lock();
                    if(res.flag){
                        res.condition.await();
                    }
                    if (count == 0) {
                        res.name = "小明";
                        res.sex = "男";
                    } else {
                        res.name = "小红";
                        res.sex = "女";
                    }
                    count = (count + 1) % 2;
                    res.flag=true;
                    res.condition.signal();
                } catch (Exception e) {
                } finally {
                    res.lock.unlock();
                }
    
            }
    
        }
    
    }
    
    // 读取线程
    class OutThread extends Thread {
    
        public Res res;
    
        public OutThread(Res res) {
            this.res = res;
        }
    
        @Override
        public void run() {
            while (true) {
    
                try {
                    res.lock.lock();
                    if(!res.flag){
                        res.condition.await();
                    }
                    Thread.sleep(1000);
                    System.out.println(res.name + "," + res.sex);
                    res.flag = false;
                    res.condition.signal();
                } catch (Exception e) {
                    // TODO: handle exception
                } finally {
                    res.lock.unlock();
                }
            }
    
        }
    }
    

    3.3 Lock与synchronized 比较

    1. 锁的实现:synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
    2. 性能:新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
    3. 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock 可中断,而 synchronized 不行。
    4. 公平锁,公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
    5. 锁绑定多个条件:一个 ReentrantLock 可以同时绑定多个 Condition 对象。
    6. 使用选择
      • 除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
  • 相关阅读:
    CRM 客户线索 与 销售人员
    CRM X
    MySQL为Null导致的5大坑
    搞懂 macOS 上的主机名/hostname/ComputerName
    Node服务中如何写日志?
    Linux下 iptables 超详细教程和使用示例
    精读《Prisma 的使用》
    Redis夺命20问
    redis HyperLogLog的使用
    聊聊redis分布式锁的8大坑
  • 原文地址:https://www.cnblogs.com/haoworld/p/java-bing-fa-xian-cheng-tong-xin.html
Copyright © 2011-2022 走看看