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

posted @ 2022-02-05 12:23  zeroingToOne  阅读(2296)  评论(0编辑  收藏  举报