Java线程通信

线程通信是Java线程部分的重点,我们介绍一下常见的几种线程通信方式。

线程锁与同步

锁机制是线程通信的一种重要方式。当多个线程竞争某一个对象时,一旦某个线程获得对象就会立刻将其上锁,其他线程只能等待锁被释放才可以继续竞争。当我们需要进行多个线程间的同步时,经常利用线程锁完成。

在下面的代码中,两个线程总会有一个执行先后顺序,但后执行的线程必须等待先执行的代码运行结束才可以执行。

public class ObjectLock {
	
	private static Object obj = new Object();
	
	static class MyRunnableA implements Runnable {
		@Override
		public void run() {
			synchronized (obj) {
				for (int i = 1; i <= 100; ++i) System.out.println("MyRunnableA: " + i);
			}
		}
	}
	
	static class MyRunnableB implements Runnable {
		@Override
		public void run() {
			synchronized (obj) {
				for (int i = 1; i <= 100; ++i) System.out.println("MyRunnableB: " + i);
			}
		}
	}
	
	public static void main(String[] args) {
		new Thread(new MyRunnableA(), "线程1").start();
		new Thread(new MyRunnableB(), "线程2").start();
	}
}

结果显示一个线程运行结束了以后才可以运行下一个线程。

等待/通知机制

等待/通知机制同样是线程通信方式中比较重要的方法,这里的等待使用的是Object类中的wait()方法,通知使用的是Object类中的notify()notifyAll()方法,以上三个方法都必须在synchronized中使用。notify()方法一次只能随机唤醒一个等待线程,而notifyAll()会唤醒所有的等待线程。当线程使用wait()方法时,会自动释放占有的锁资源。我们用代码来查看一下。

public class WaitAndNotify {
	
	private static Object obj = new Object();
	
	static class MyRunnableA implements Runnable {
		@Override
		public void run() {
			synchronized (obj) {
				for (int i = 0; i < 5; ++i) {
					System.out.println("MyRunnableA: " + i);
					obj.notify();
					try {
						obj.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				obj.notify();
			}
		}
	}
	
	static class MyRunnableB implements Runnable {
		@Override
		public void run() {
			synchronized (obj) {
				for (int i = 0; i < 5; ++i) {
					System.out.println("MyRunnableB: " + i);
					obj.notify();
					try {
						obj.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				obj.notify();
			}
		}
	}

	public static void main(String[] args) {
		new Thread(new MyRunnableA(), "线程1").start();
		new Thread(new MyRunnableB(), "线程2").start();
	}
}

上面的代码交替等待和唤醒对方从而实现交替打印的效果,结果如下。

volatile共享内存

volatile关键字保证了线程间的内存可见性,本文暂不展开讲,后面会有专门的文章讲volatile和JMM(Java内存模型)相关的内容。

public class VolatileDemo {
	
	private static volatile boolean flag = true;
	
	static class MyRunnableA implements Runnable {
		@Override
		public void run() {
			System.out.println("start time: " + System.currentTimeMillis());
			while (flag) ;
			System.out.println("flag已被置为false");
			System.out.println("end time: " + System.currentTimeMillis());
		}
	}
	
	static class MyRunnableB implements Runnable {
		@Override
		public void run() {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("flag即将被置为false");
			flag = false;
		}
	}
	
	public static void main(String[] args) {
		new Thread(new MyRunnableA(), "线程1").start();
		new Thread(new MyRunnableB(), "线程2").start();
	}
}

以上的代码线程1初始时会阻塞在while循环里,但是线程2休眠1秒后将主内存中的flag改成了false,线程1第一时间获取了主内存的变量值变化并退出循环。具体结果如下:

管道

Java提供了四个类来实现管道,分别是PipedReaderPipedWriterPipedInputStreamPipedOutputStream。前两个是面向字符的,后两个是面向字节的。下面的代码实现了一个简单的管道通信,模拟了C/S结构的通信。

public class Pipeline {
	
	private static PipedReader reader = new PipedReader();
	private static PipedWriter writer = new PipedWriter();
	
	static class Server implements Runnable {

		@Override
		public void run() {
			try {
				
				int receivedData = 0;
				while ((receivedData = reader.read()) != -1) {
					System.out.print((char)receivedData);
				}
				System.out.println();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	static class Client implements Runnable {

		@Override
		public void run() {
			try {
				writer.write("test");
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException, IOException {
		writer.connect(reader);
		new Thread(new Server()).start();
		TimeUnit.SECONDS.sleep(1);
		new Thread(new Client()).start();
	}
}

信号量

学过操作系统的同学们一定对信号量的概念不陌生,在Java中有一个定义信号量的工具类Semaphore。我们以一个经典的生产者-消费者模型来测试一下这种通信方式。

public class SemaphoreDemo {
	
	static class Producer implements Runnable {
		
		Semaphore mutex;
		Semaphore empty;
		Semaphore full;
		
		public Producer(Semaphore mutex, Semaphore empty, Semaphore full) {
			this.mutex = mutex;
			this.empty = empty;
			this.full = full;
		}

		@Override
		public void run() {
			while (true) {
				try {
					System.out.println("生产一些东西");
					TimeUnit.SECONDS.sleep(1);
					empty.acquire();
					mutex.acquire();
					System.out.println("将数据放入缓冲中");
					TimeUnit.SECONDS.sleep(1);
					mutex.release();
					full.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	static class Consumer implements Runnable {
		Semaphore mutex;
		Semaphore empty;
		Semaphore full;
		
		public Consumer(Semaphore mutex, Semaphore empty, Semaphore full) {
			this.mutex = mutex;
			this.empty = empty;
			this.full = full;
		}
		@Override
		public void run() {
			while (true) {
				try {
					full.acquire();
					mutex.acquire();
					System.out.println("将数据从缓冲中取出");
					TimeUnit.SECONDS.sleep(1);
					mutex.release();
					empty.release();
					System.out.println("消费一些东西");
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Semaphore mutex = new Semaphore(1);
		Semaphore empty = new Semaphore(10);
		Semaphore full = new Semaphore(0);
		new Thread(new Producer(mutex, empty, full)).start();
		new Thread(new Consumer(mutex, empty, full)).start();
	}
}

上面的代码模拟了经典的生产者-消费者模型,生产者一直在生产产品,消费者一直在消费产品,我们可以得到下面的运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值