ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java基础-线程通信

2021-12-30 18:33:39  阅读:148  来源: 互联网

标签:java Thread 包子 通信 System consumerThread 线程 public


目录

1、通信的方式:

1.1、文件共享

1.2、变量共享

1.3、线程协作-JDK API

1.3.1suspend/remuse

1.3.2 wait/notify机制

 1.3.3 park/unpark机制

2、总结

3、伪唤醒

 4、结语


1、通信的方式:

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

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

①文件共享

②网络共享

③共享变量

④JDK提供的线程协调API:suspend/resume、wait/notify、park/unpark

1.1、文件共享

线程1写数据到文件里,线程2读取文件中的数据内容,实现数据的交换 

 代码示例:

import java.nio.file.Files;
import java.nio.file.Paths;

public class Text {

       //共享文件
    public static String filePath= "text.txt";

    public static void main(String[] args) throws Exception {

        //线程1写入数据
        new Thread(()->{
           try{

               while(true){
                   Files.write(Paths.get(filePath),("当前时间" + String.valueOf(System.currentTimeMillis())).getBytes());
                   Thread.sleep(1000L);
               }

           }catch (Exception e){
               e.printStackTrace();
           }
        }).start();

        //线程2,读数据
        new Thread(() ->{

            try{
                while(true){
                    Thread.sleep(1000L);
                    byte[] allBytes = Files.readAllBytes(Paths.get(filePath));
                    System.out.println(new String(allBytes));
                }
            }catch (Exception e){
                e.printStackTrace();
            }

        }).start();
    }
}

1.2、变量共享

 线程1写数据到内存里(某个变量),线程2读取内存中(某个变量)的数据内容,实现数据的交换 

代码示例:

public class Text {

    //共享变量
    public static String content = "";

    public static void main(String[] args) throws Exception {

        //线程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(new String(content));
                }
            }catch (Exception e){
                e.printStackTrace();
            }

        }).start();
    }
}

1.3、线程协作-JDK API

JDK中对于需要多线程协作完成某一任务场景,提供了对于API支持。

多线程协作的典型场景:生产者-消费者模型。(线程阻塞,线程唤醒)

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

场景:线程1买包子,包子店没有包子则等待。线程2生产包子,并通知线程1可以买包子了。 

1.3.1suspend/remuse

API-被弃用的suspend挂起目标线程,通过remuse可以恢复线程执行。

太容易产生死锁,所以被弃用

正常用法:

public class Text {

    //共享变量
    public static Object baozidian = null;

    public static void main(String[] args) throws Exception {

        Thread consumerThread = new Thread(()->{

            try{

                if (baozidian == null){

                    System.out.println("暂时没有包子,进入等待...");
                    Thread.currentThread().suspend();//消费者卡在这,等待通知
                }
                System.out.println("买到包子 回家!");

            }catch (Exception e){
                e.printStackTrace();
            }

        });

        consumerThread.start();
        //主线程等待3秒,再生产包子。 让thread线程先执行
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();
        System.out.println("生产了包子,通知消费者可以购买!");
    }
}

结果:

suspend/remuse死锁写法: 

第一种:

死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码

public class Text {

    //共享变量
    public static Object baozidian = null;

    public static void main(String[] args) throws Exception {

        Thread consumerThread = new Thread(()->{

            try{

                if (baozidian == null){
                    System.out.println("暂时没有包子,进入等待...");
                    synchronized(Text.class){ //拿到锁,
                        Thread.currentThread().suspend();//挂起阻塞,并没有释放锁
                    }


                }
                System.out.println("买到包子 回家!");

            }catch (Exception e){
                e.printStackTrace();
            }

        });

        consumerThread.start();
        //主线程等待3秒,再生产包子。 让consumerThread线程先执行
        Thread.sleep(3000L);
        baozidian = new Object();
        synchronized(Text.class){ //consumerThread没有释放锁,拿不到锁。
            consumerThread.resume();//拿不到锁  唤醒不了consumerThread。产生死锁
        }

        System.out.println("生产了包子,通知消费者可以购买!");
    }
}

结果:

consumerThread 线程拿到锁,没有释放就挂起。主线程拿不到锁,不能唤醒consumerThread线程,从而 产生死锁

suspend/remuse死锁写法: 

第二种:

remuse先通知唤醒线程consumerThread。suspend后面又使线程consumerThread挂起。得不到通知,导致永久挂起阻塞不能执行。

public class Text {

    //共享变量
    public static Object baozidian = null;

    public static void main(String[] args) throws Exception {

        Thread consumerThread = new Thread(()->{

                if (baozidian == null){
                    System.out.println("暂时没有包子,进入等待...");

                    try{
                        Thread.sleep(5000L); //模拟处理时间,等待5秒
                    }catch (Exception e){
                        e.printStackTrace();
                    }

                    Thread.currentThread().suspend();//挂起阻塞
                }
                System.out.println("买到包子 回家!");
        });

        consumerThread.start();
        //主线程等待3秒,再生产包子。 让consumerThread线程先执行
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();//通知 consumerThread 执行

        System.out.println("生产了包子,通知消费者可以购买!");
    }
}

1.3.2 wait/notify机制

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

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

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

推荐理由:wait可以自动解锁,但是对顺序执行有要求,需要先wait后notify

注意:

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

public class Text {
    
    public static Object baozidian = null;

    /** 正常的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、通知消费者");
        }
    }


    public static void main(String[] args) throws Exception {
        Text text = new Text();

        text.waitNotifyTest();;
    }
}

上述代码:先执行wait方法使线程等待,后执行notifyAll方法唤醒所有等待的线程。

 导致程序永久等待的wait/notify

notify先通知唤醒,后面执行wait使线程等待。notify已经执行,不再唤醒,永远等待

但是wait释放锁,所以比较好

public class Text {

    public static Object baozidian = null;


    /** 会导致程序永久等待的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、通知消费者");
        }
    }


    public static void main(String[] args) throws Exception {
        Text text = new Text();


        text.waitNotifyDeadLockTest();
    }
}

 1.3.3 park/unpark机制

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

不要求park/unpark的调用顺序。

多次调用unpark之后,再调用park,线程会直接运行。

多次调用unpark,只能得到一次“许可”,不会叠加许可。再调用park获得“许可”,直接运行,消耗一次许可。再调用park,线程则进入等待状态。当unpark又提供一次许可。则线程继续执行一次。

正常的park/unpark 

LockSupport.park()将当前线程,也就是消费者线程挂起,3秒之后,主线程 LockSupport.unpark(consumerThread);指定消费者线程继续执行。

执行的先后顺序,不影响。


    /** 正常的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

park并不是基于监视器锁的方式实现的,jvm底层提供的另外一种线程挂起方式。

park拿到锁,使当前消费者线程挂起。 unpark拿不到锁,无法是消费者线程继续执行

/** 死锁的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、通知消费者");
    }

2、总结

在同步代码块中:

suspend/resume用法:(被弃用)

对同步锁的使用有要求,不会释放锁,容易死锁。对顺序有要求,先调用suspend后resume。容易导致永久挂起。

wait/notify用法:

对同步锁没有要求,wait方法会自动释放锁。对顺序有要求,先调用wait后notify。容易导致永久挂起。

park/unpark用法:

对同步锁的使用有要求,不会释放锁,容易死锁。对顺序没有要求,park挂起消费者线程,unpak给消费者线程执行“许可”后,消费者线程继续执行。

虽然都有缺陷,但是都比已经弃用的suspend/resume要好。

3、伪唤醒

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

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

伪唤醒是指:线程并非是notify、notifyAll、unpark等api调用而唤醒的,是更底层的原因导致的。

 在上面所有例子中,将if改为while,防止伪唤醒。

 4、结语

本章内容,设计很多JDK多线程开发工具类,它底层实现的原理。

标签:java,Thread,包子,通信,System,consumerThread,线程,public
来源: https://blog.csdn.net/LemonSnm/article/details/122219055

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有