天天看点

多线程之线程之间的通信

一、线程通信的概念

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效地把控与监督。

二、线程通信的实现方式

使用wait/notify方法实现线程间的通信。(这两个方法都是object类的方法,即java所有的对象都提供了这两个方法)

1.wait/notify必须配合synchronized关键字使用

2.wait方法释放锁,notify方法不释放锁。

三、示例

import java.util.ArrayList;
import java.util.List;
/**
 * 线程之间的通信
 * @author shixiangcheng
 * 2019-06-29
 */
public class Test {
	private volatile static List list=new ArrayList();
	public void add(){
		list.add("test");
	}
	public int size(){
		return list.size();
	}
	public static void main(String[] args) throws Exception{
		final Test test=new Test();
		//实例化一个lock,使用wait和notify时,一定要配合synchronized关键字
		final Object lock=new Object();
		Thread t1=new Thread(new Runnable(){
			@Override
			public void run() {
				try{
					synchronized(lock){
						for(int i=0;i<10;i++){
							test.add();
							System.out.println("当前线程:"+Thread.currentThread().getName()+" 添加了一个元素");
							Thread.sleep(500);
							if(test.size()==5){
								System.out.println("已发出通知。。");
								lock.notify();
							}
							Thread.sleep(100);
						}
					}
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		},"t1");
		
		Thread t2=new Thread(new Runnable(){
			@Override
			public void run() {
				synchronized(lock){
					if(test.size()!=5){
						System.out.println("t2开始。。");
						try {
							Thread.sleep(500);
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("当前线程:"+Thread.currentThread().getName()+"收到通知线程停止");
					throw new RuntimeException();
				}
			}
		},"t2");
		t2.start();
		Thread.sleep(100);//保证t2先于t1启动
		t1.start();
	}
}
           

运行结果

t2开始。。
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
已发出通知。。
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t2收到通知线程停止
Exception in thread "t2" java.lang.RuntimeException
	at com.shi.sync005.Test$2.run(Test.java:56)
	at java.lang.Thread.run(Thread.java:745)

           

分析上面结果得知,当t1发出通知后,t2并没有立即停止。而是t1执行完后,t2才停止。原因就是:notify方法不释放锁。

1.试问如何优化才可以提高效率,使得t1通知后,t2及时停止呢?

可以对t1做如下修改,将锁加到for循环里面,使原子性的粒度更细一些。

Thread t1=new Thread(new Runnable(){
			@Override
			public void run() {
				try{
//					synchronized(lock){
						for(int i=0;i<10;i++){
							synchronized(lock){
								test.add();
								System.out.println("当前线程:"+Thread.currentThread().getName()+" 添加了一个元素");
								Thread.sleep(500);
								if(test.size()==5){
									System.out.println("已发出通知。。");
									lock.notify();
								}
							}
							Thread.sleep(100);
						}
//					}
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		},"t1");
           

执行结果如下

t2开始。。
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
已发出通知。。
Exception in thread "t2" 当前线程:t2收到通知线程停止
java.lang.RuntimeException
	at com.shi.sync005.Test$2.run(Test.java:60)
	at java.lang.Thread.run(Thread.java:745)
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
           

2.除了上面方案,是否还有其它方案?

可以使用java.util.concurrent.CountDownLatch 本身没有任何锁。

代码如下

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * 线程之间的通信
 * @author shixiangcheng
 * 2019-06-29
 */
public class Test {
	private volatile static List list=new ArrayList();
	public void add(){
		list.add("test");
	}
	public int size(){
		return list.size();
	}
	public static void main(String[] args) throws Exception{
		final Test test=new Test();
		//实例化一个lock,使用wait和notify时,一定要配合synchronized关键字
		final CountDownLatch countDownLatch=new CountDownLatch(1);
		Thread t1=new Thread(new Runnable(){
			@Override
			public void run() {
				try{
					for(int i=0;i<10;i++){
						test.add();
						System.out.println("当前线程:"+Thread.currentThread().getName()+" 添加了一个元素");
						Thread.sleep(500);
						if(test.size()==5){
							System.out.println("已发出通知。。");
							countDownLatch.countDown();
						}
						Thread.sleep(100);
					}
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		},"t1");
		
		Thread t2=new Thread(new Runnable(){
			@Override
			public void run() {
				if(test.size()!=5){
					System.out.println("t2开始。。");
					try {
						Thread.sleep(500);
						countDownLatch.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("当前线程:"+Thread.currentThread().getName()+"收到通知线程停止");
				throw new RuntimeException();
			}
		},"t2");
		t2.start();
		Thread.sleep(100);//保证t2先于t1启动
		t1.start();
	}
}
           

执行结果

t2开始。。
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
已发出通知。。
当前线程:t2收到通知线程停止
Exception in thread "t2" java.lang.RuntimeException
	at com.shi.sync005.Test$2.run(Test.java:56)
	at java.lang.Thread.run(Thread.java:745)
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
当前线程:t1 添加了一个元素
           

四、列举一个应用场景

zk client端连接zk集群,和连接数据库类似。zk client向集群发送请求,集群返回连接,证明连接可用。然后zk对象才能实例化出来,只有拿到真正的实例化对象,才可以继续向下操作。

多线程之线程之间的通信

图示47行代码标识主线程阻塞,待zk对象实例化成功后,通过回调函数process来通知主线程继续执行。

--------------------------------------------END--------------------------------------------

继续阅读