• 线程通信


    线程通信

    学习材料来源于网络
    如有侵权,联系删除

    要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。

    涉及到线程之间相互通信,分为下面四类:

    • 文件共享
    • 网络共享
    • 共享变量
    • jdk提供的线程协调API

    JDK提供的线程线程通信API: suspend()/resume()、wait()/notify()、park()/unpark()。

    文件共享

    文件共享主要使用文件系统的读写分离机制来实现线程通信。

    
        public static void main(String args){
            //线程1-写入数据
            new Thread(() ->{try {
                while (true){
                    Files.write(Paths.get("Demo7.log"),
                            ("当前时间" + String.valueOf(System.currentTimeMillis())).getBytes());
                    Thread.sleep(1000L);
                }
            } catch (Exception e){e.printStackTrace();}
            }).start();
        //线程2-读取数据new 
        new Thread(()->{try {
                while (true){
                    Thread.sleep(1000L);
                    byte[] allBytes= Files.readAllBytes(Paths.get("Demo7.log"));
                    System.out.println(new String(allBytes));
                }
            } catch (Exception e){e.printStackTrace();}
            }).start();
        }
    
    

    变量共享

    //共享变量
    public static String content="空";
    public static void main(String args){ //线程1-写入数据
        new Thread(()-> {
            try {
                while (true) {
                    content = "当前时间" + String.valueOf(System.currentTimeMillis());
                    Thread.sleep(1000L);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        //线程2-读取数据
        new Thread(() ->{
            try {
                while (true){
                    Thread.sleep(1000L);
                    System.out.println(content);
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }).start();
    }
    

    JDK线程协作API

    JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、线程唤醒)

    示例:线程1去买包子,没有包子,则不再执行。线程-2生产出包子,通知线程-1继续执行。

    suspend()与resume()

    示例:

    /** 包子店 */
    public static Object baozidian = null;
    
    /** 正常的suspend/resume */
    public void suspendResumeTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
    }
    

    死锁实列:

    /** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
    public void suspendResumeDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3、通知消费者");
    }
    

    说明:

    消费者拿到当前锁this后,处于挂起状态,其他线程想抢到锁后才能唤醒,这种业务逻辑就会出现死锁情况,因为线程 suspend()的时候是不会释放当前的锁的。

    通知的先后顺序也会使得线程进入死锁状态:

    /** 导致程序永久挂起的suspend/resume */
    public void suspendResumeDeadLockTest2() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) {
                System.out.println("1、没包子,进入等待");
                try { // 为这个线程加上一点延时
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 这里的挂起执行在resume后面
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
        consumerThread.join();
    }
    

    运行结果

    consumerThread执行的时候,main线程先处于暂停3秒状态,consumerThread线程判断没有包子,等待5秒,在consumerThread暂停5秒的时候,这个时候在主线程结束暂停后,通知consumerThread线程唤醒,但是consumerThread线程还在暂停时间里面,没有进入休眠,所以不会被唤醒,等consumerThread线程停止5秒时间过后,进入休眠,这个时候main线程已经唤醒过了。所以consumerThread线程会一直处于休眠状态。

    wait()与notify()

    这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出lllegalMonitorStateException异常。

    wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。

    notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

    注意∶虽然会wait自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。

    示例:

     /** 正常的wait/notify */
    public void waitNotifyTest() throws Exception {
        // 启动线程
        new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                synchronized (this) {
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到包子,回家");
        }).start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }
    

    需要明白的是使用wait的时候,线程会把锁释放

    先后顺序造成死锁的问题

    /** 会导致程序永久等待的wait/notify */
    public void waitNotifyDeadLockTest() throws Exception {
        // 启动线程
        new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                synchronized (this) {
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到包子,回家");
        }).start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }
    

    说明

    这种问题的死锁是因为先后调用顺序
    

    park()与unpark()

    不要求park和unpark方法的调用顺序。

    多次调用unpark之后,再调用park,线程会直接运行。但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。

    线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”。

    示例:

    /** 正常的park/unpark */
    public void parkUnparkTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                LockSupport.park();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        LockSupport.unpark(consumerThread);
        System.out.println("3、通知消费者");
    }
    

    运行结果

    死锁示例:

    /** 死锁的park/unpark */
    public void parkUnparkDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    LockSupport.park();
                }
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知消费者");
    }
    

    运行结果:

    这种死锁的问题是因为线程在park()的时候,不会释放当前的锁。

    伪唤醒

    警告!之前代码中用if语句来判断,是否进入等待状态,是错误的!

    官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

    伪唤醒是指线程并非因为notifynotifyallunpark等api调用而唤醒,是更底层原因导致的。

    // wait
    synchronized (obj){
        while (<条件判断>){
            obj.wait();
            //执行后续操作
        }
    }
    // park
    while(<条件判断>){
        LockSupport.park();
        //执行后续操作
    }
    
    记得加油学习哦^_^
  • 相关阅读:
    如何简单的管理API
    API管理工具的选择
    项目开发效率怎么提高——围绕API接口文档
    VIM编辑器之常用命令
    ASP.NET 2.0的会员、角色及配置问题的探讨
    常用函数和方法集
    ASP.NET 2.0中实现跨页面提交
    ASP.NET 2.0站点登录、导航与权限管理
    以编程方式使用 ASP.NET 母版页
    如果你想学好C#!就一定要看这个!对你很多帮助!
  • 原文地址:https://www.cnblogs.com/shaoyayu/p/14073858.html
Copyright © 2020-2023  润新知