• Java线程间的通信方式


    1. 同步,多个线程通过synchronized关键字实现线程间的通信。(个人理解:使用synchronized和第3种方法使用wait/notify是同一种方式)

    例子:子线程循环3次,接着主线程循环5次,接着又回到子线程循环3次,接着在回到主线程循环5次,如此循环4次

    代码实现:

    public class SychronizedTest {
        
        public static void main(String[] args) {
    
            final Business business = new Business();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    for (int j = 1; j <= 4; j++) {
                        business.sub(j);
                    }
                }
            }).start();
            for (int j = 1; j <= 4; j++) {
                business.main(j);
            }
    
        }
    
    }
    
    class Business {
        private boolean bshouldSub = true;// 子线程和主线程通信信号
    
        public synchronized void sub(int j) {
            if (!bshouldSub) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            }
            for (int i = 1; i <= 3; i++) {
                System.out.println("sub sthread sequece of " + i + ",loop of " + j);
    
            }
            bshouldSub = false;// 运行结束,设置值为FALSE 让主程序运行
            this.notify();// 唤醒等待的程序
    
        }
    
        public synchronized void main(int j) {
            if (bshouldSub) {// 如果bshouldsub=true ,等待, 让子程序运行
                try {
                    this.wait();
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            }
            for (int i = 1; i <= 5; i++) {
                System.out.println("main sthread sequece of " + i + ",loop of " + j);
            }
            bshouldSub = true;// 让子程序运行
            this.notify();// 唤醒等待的一个程序
        }
    
    }

    在business中,使用synchronized实现两个方法互斥。然后,在实现两个方法的交替出现。即实现主线程和子线程的互相通信。这样就非常简单,我们可以引入一个内部变量,bshouldSub ,如果bshouldSub是true,则执行子线程,否则等待。如果是flase则执行主线程,否则主线程等待。在执行主线程之后,将bshouldSub设置为true,并唤醒等待程序,子线程执行同理。

    运行结果:

    参考:https://blog.csdn.net/lu930124/article/details/51242382

    2. while轮询的方式

    代码实现:

    public class WhileTest {
        public static List<Object> list = new ArrayList<Object>();
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    System.out.println("线程1启动");
                    for (int i = 0; i < 10; i++) {
                        list.add(new Object());
                        System.out.println("添加了第 " + (i + 1) + " 个元素");
                        try {
                            Thread.sleep(1000);//保证thread1每添加一个元素之和,thread2都能访问到list.size(),避免thread1已经在list中添加了5个以上的元素,而thread2还没访问到list.size()
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("线程1完成任务退出");
                }
    
            };
            
            Thread t2 = new Thread() {
                @Override
                public void run() {
                    System.out.println("线程2启动");
                    while (true) {
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("list.size = " + list.size());
                        if (list.size() == 5) {
                            System.out.println("执行线程2业务");
                            break;
                        }
                    }
                    System.out.println("线程2完成任务退出");
                }
    
            };
            t1.start();
            t2.start();
    
        }
    }

    在这种方式下,线程t1不断地改变条件,线程t2不停地通过while语句检测这个条件(list.size()==3)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。

    运行结果:

    参考:https://blog.csdn.net/diwangerdai/article/details/72724315    https://www.cnblogs.com/hapjin/p/5492619.html

    3. wait/notify机制(等待/通知机制)

    Java为每一个Object都实现了wait()和notify()方法,他们必须用于同步方法或同步代码块内。

    方法wait()的作用是使当前执行代码的线程进入等待,它是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并在wait()所在的代码行停止执行,直到接到通知或被中断为止。

    在调用wait()之前,线程需要获得该对象的对象级别锁,所以只能在同步方法或同步块中调用wait()方法。执行wait()后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,抛出IllegalMonitorstateException。

    方法notify()也要在同步方法或同步块中调用,在调用前,线程也要获得该对象的对象级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程,对其发出notify,并使他获取该对象的对象锁。

    在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也即是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态的线程才可能获取该对象锁。

    当第一个获取该对象锁的wait线程运行完毕后,他会释放该对象锁,此时如果该对象没有再次使用notify语句,即使该对象已经空闲,其他wait状态的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify/notifyall。

    notify()和notifyAll()区别:
    notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个“线程。
    notifyAll()方法可以使所有正在等待队列中等待同一资源的"全部"线程从等待状态中退出,进入可运行状态。

    代码实现:

    public class WaitNotifyTest {
        public static void main(String[] args) throws InterruptedException {
            Object lock = new Object();
            MyThread1 t1 = new MyThread1(lock);
            t1.start();
            Thread.sleep(3000);//休眠3秒。Thread.sleep(long millis)中millis单位为毫秒
            MyThread2 t2 = new MyThread2(lock);
            t2.start();
        }
    }
    
    class MyThread1 extends Thread {
        private Object lock;
    
        public MyThread1(Object lock) {
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println("begin wait   " + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("end wait     " + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    }
    
    class MyThread2 extends Thread {
        private Object lock;
    
        public MyThread2(Object lock) {
            this.lock = lock;
        }
    
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify " + System.currentTimeMillis());
                lock.notify();
                System.out.println("end notify   " + System.currentTimeMillis());
            }
        }
    }

    运行结果:

    参考:https://blog.csdn.net/lin20044140410/article/details/79074847

    4. 管道通信

    管道就是连接两个线程通讯的缓冲区,如图:

    写者进程把自己的数据通过管道输出流写入管道,读者进程再从管道通过管道输入流拿管道里面的数据
    当然进程与进程之间传递数据未必通过这个方式去传递数据,完全可以在一个进程中设置一个public变量,然后另一个进程访问,当然这样可能会耦合度高
    管道在线程之间传递数据传递数据,只是其中一个方法。
    例子:写者进程每250毫秒工作一次,不停地对管道输出数据,直到输出到5;读者则每500毫秒工作一次,不停地从管道读取数据,直到读完
    代码实现:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PipedInputStream;
    import java.io.PipedOutputStream;
    import java.io.PrintStream;
    
    public class PipeThreadTest {
        public static void main(String args[]) throws IOException {
            PipedOutputStream pos = new PipedOutputStream();
            PipedInputStream pis = new PipedInputStream(pos);
            new Writer(pos).start();
            new Reader(pis).start();
        }
    }
    
    class Writer extends Thread {
        private PipedOutputStream pos;
    
        public Writer(PipedOutputStream pos) {
            this.pos = pos;
        }
    
        public void run() {
            PrintStream p = new PrintStream(pos);
            for (int i = 1; i < 6; i++) {
                try {
                    Thread.currentThread().sleep(250);
                } catch (Exception e) {
                }
                p.println(i);
                System.out.println("Write:" + i);
            }
            System.out.println("已经写入完毕");
            p.flush();
            p.close();
        }
    }
    
    class Reader extends Thread {
        private PipedInputStream pis;
        private String line;
    
        public Reader(PipedInputStream pis) {
            this.pis = pis;
        }
    
        public void run() {
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                do {
                    line = r.readLine();
                    if (line != null)
                        System.out.println("Read:" + line);
                    else
                        System.out.println("已经读取完毕");
                    Thread.currentThread().sleep(500);
                } while (r != null && line != null);
            } catch (Exception e) {
            }
        }
    }

    运行结果:

    参考:https://blog.csdn.net/yongh701/article/details/42914669

  • 相关阅读:
    【IT笔试面试题整理】把n个骰子扔在地上,所有骰子朝上一面的点数之和为S概率转
    面试题位操作
    微软面试题 寻找数组中出现的唯一重复的一个数
    【IT笔试面试题整理】给定二叉树先序中序,建立二叉树的递归算法
    【IT笔试面试题整理】 二叉树任意两个节点间最大距离
    面试题堆栈和队列
    LRU cache实现 Java 转
    【IT笔试面试题整理】有序数组生成最小高度二叉树
    Unity3d Asset Store下载的资源在哪?
    Xcode 6 如何将 模拟器(simulator) for iphone/ipad 转变成 simulator for iphone
  • 原文地址:https://www.cnblogs.com/zeroingToOne/p/9539138.html
Copyright © 2020-2023  润新知