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) { } } }
运行结果: