多线程安全问题之Wait和Notify的配合使用

notify 和 wait 线程配合

线程一直是Java编程很有难度的一个部分,线程安全,线程之间的相互配合是保证程序能够正常运行的基础,特别是高并发量高吞吐量的运行过程中,今天就用C-S 中的两个线程之间的配合,谈谈我对notify 和wait使用的理解。

CSFramework的基本类之间的关系

我们想通过c-s来学习wait和notify的应用自然要先了解里面的基本工作过程:
Communication是C-S中的通信类实现了Runable,负责建立通信信道,接收对端消息。
服务器正常启动之后必然要开启侦听线程来侦听客户机的连接,一旦服务器成功侦听到有客户机的连接,服务器和客户机必然都会产生处理对端消息的会话层类ServerCommunicate和ClientCommunicate类的对象,而以上两个类都继承了Communicate类。

下面是客户机连接服务器的:

public boolean confirmOnline(){
		if(serverIP == null) {
			System.out.println("IP未设置");
			return  false;
		}
		if(port == 0) {
			System.out.println("端口未设置");
			return  false;
		}
		boolean connect = true;
			try {
				socket = new Socket(serverIP, port);
				//客户机连接服务器时要new一个自己的ClientCommunicate
				**clientCommunicate = new ClientCommunicate(socket, this);**
			} catch (IOException e) {
				connect = false;
			}
		return connect;
	}

wait和notify配合的必要性

解释了服务器和客户机以及他们对应的会话层和通信层之后,我们就以服务器端为例看看notify和wait在这里的应用。

这里是Server侦听到客服及连接自己的操作:

while(goon){
	//		try {
				senMessageToListener(Util.getCurrentTime(Util.DATE_TIME) + "开始侦听客户机连接");
				Socket socket;
				try {
					socket = serverSocket.accept();
					ServerCommunicate serverCommunicate = new ServerCommunicate(socket, this);
// 暂且不看下面用内部类来处里客户机请求的部分,上面的两行代码是指,一旦服务器侦听到客户机连接就会有一个和这个客户机线程对应的ServerCommunication类来处理。
//					synchronized(serverCommunicatesPool){
//						serverCommunicatesPool.add(serverCommunicate);
//					}
//				} catch (IOException e) {
//					goon = false;
//					e.printStackTrace();
//				}
public ServerCommunicate(Socket socket, Server server) throws IOException {
		super(socket);
		System.out.println("@@@@@" + Thread.currentThread().getName());
		this.server = server; 
		this.socketer = socket;
	}

上面代码是ServerCommunicate类的构造方法,当运行客户机成功连接服务器后输出的结果是在这里插入图片描述
说明ServerCommunication的初始化 是在服务器侦听线程下执行再看Communication类的构造方法:

public Communicate(Socket socket) throws IOException {
		this.socket = socket;
		this.dis = new DataInputStream(socket.getInputStream());
		this.dos = new DataOutputStream(socket.getOutputStream());
		goon = true;
		System.out.println("2222" + Thread.currentThread().getName());
		new Thread(this, "开启会话线程").start();
	}

我们发现输出如下:ServerCommunicate和Communicate的构造是在一个线程下执行的,并在Communicate执行构造方法时开启了会话线程来接收消息。很显然这里至少存在着两个线程的相互配合问题
在这里插入图片描述
刚上线成功时,服务器要把客户端ID发送过去,必然会调用Communication里面提供的Sendmessage方法,这个方法的调用是由服务器侦听线程在ServerCommunicate里面调用的。相应的客户机收到后立刻给服务器返回了一个确认登陆的消息,由服务器端的开启会话线程接收到服务器的反馈后处理信息,这时候会存在一种状况:当执行在Communicaton的构造方法时,通过start方法开启会话线程后,会话线程进入NEW的状态,此时客户端要是发来消息,就会接收不到消息。
因此我们需要让会话线程真正意义上开启之后即进入就绪态或者运行态时,在完成对Communication的初始化,这样就能保证在服务器在没有完全开启会话线程的时候是不能够调用sendMessage发送信息的,因此也不会受到对端返回的消息。下面是Communication里面的run方法:

@Override
	public void run() {
		System.out.println("11111" + Thread.currentThread().getName());
		String message = "";
		while(goon){
			try {
				message = dis.readUTF();
				dealMessage(new NetMessage(message));
			} catch (IOException e) {
				if(goon == true){
					dealOppositeDrop();
				}
				goon = false;
			}
		}
		
		closeCommunicate();
		
	}

Wait和Notify的作用。

下面是加了wait之后Communication的构造方法:

Communication(Socket socket) {
		try {
			this.lock = new Object();
			this.socket = socket;
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
			synchronized (lock) {
				this.goon = true;
				new Thread(this, "开启会话线程").start();
				// 如果这里也存在代码,则,它们一定在子线程真正运行之前执行。
				try {
					lock.wait();
					// wait()方法,在阻塞自身线程前,必然开lock锁;
					// 当该线程被其它线程notify()后,将再次进入lock锁块,而进一步对lock上锁。
				} catch (InterruptedException e) {
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

下面是notify的使用部分:

@Override
	public void run() {
		String message = null;
		synchronized (lock) {
			lock.notify();
		}
		
		while (goon) {
			try {
				message = dis.readUTF();
				dealNetMessage(new NetMessage(message));
			} catch (IOException e) {
				if (goon == true) {
					goon = false;
					peerAbnormalDrop();
				}
			}
		}
		close();
	}

1.给构造方法中加入对象锁之后,如果线程运行到new Thread(this, "开启会话线程").start(); 后时间片段恰好到了,此时开启了会话线程,经过一段时间进入到了就绪态,如果它先抢占了CPU,运行到

`synchronized (lock) {
	lock.notify();
				}

的时候发现lock是锁住的,该线程就会进入到lock锁的阻塞队列中,直到服务器侦听线程重新占用CPU后,运行到构造方法中的lock.wait();服务器侦听线程会在阻塞自身线程,并开lock锁,等待被唤醒。
当会话线程再次获得cpu资源的时候,检查到锁是开着的,进入锁块后执行lock.notify();唤醒服务器侦听线程,释放锁,进入到while循环中。 这个时候已经完成了先让会话线程正常工作,
服务器侦听线程再次或得CPU资源后,进入锁块完成构造方法中的其他代码。
如此就通过wait和notify两个函数的配合保证了当会话线程正常工作之后,Communication类完成构造才能调用sendmessage方法给对端发送消息。

存在的问题

这种模式已经最大程度上减少了信息读取不到的情况,但是还不能完全排除,比如:
1.当会话线程刚刚出了锁块后,该线程已经释放了该对象锁,时间片段已经到了,此时服务器侦听线程抢占到了资源完成了初始化,并通过sendmessage方法相对端发送请求,如果此时会话线程还没有进行到while循环中,而对端的反应速度很快,就没有办法接收到消息了。

synchronized (lock) {
			lock.notify();
		}

但是wait和notify本身就存在着锁的消耗问题,所以我们也不能太苛求了,通过这个例子来了解一下它们的实战用法就好啦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值