线程间通信

同步阻塞与异步阻塞

单线程间通信

  • 轮询机制:不断地轮询,如果有数据则读取数据并处理,如果没有则等待若干时间再次轮询
  • 通知机制:如果队列中有Event,则通知工作的线程开始工作,没有Event,则工作线程休息并等待通知

wait 和 notify

简单的生产者消费者

package com.example.demo.thread;

import java.util.LinkedList;

public class EventQueue {
	private final int max;
	static class Event {

	}

	private final LinkedList<Event> eventQueue = new LinkedList<>();

	private final static int DEFAULT_MAX_EVENT = 10;

	public EventQueue() {
		this(DEFAULT_MAX_EVENT);
	}

	public EventQueue(int max) {
		this.max = max;
	}

	public void offer(Event event) {
		synchronized (eventQueue) {
			if (eventQueue.size() >= max) {
				try {
					console("the queue is full.");
					eventQueue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			console("the new event is submitted.");
			eventQueue.addLast(event);
			eventQueue.notify();
		}
	}

	public Event take() {
		synchronized (eventQueue) {
			if (eventQueue.isEmpty()) {
				try {
					console("the queue is empty.");
					eventQueue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			Event event = eventQueue.removeFirst();
			this.eventQueue.notify();
			console("the event "+ event + " is handled.");
			return event;
		}
	}

	private void console(String msg) {
		System.out.printf("%s:%s\n",Thread.currentThread().getName(), msg);
	}
}

package com.example.demo.thread;

import java.util.concurrent.TimeUnit;

public class EventClient {
	public static void main(String[] args) {
		final EventQueue eventQueue = new EventQueue();
		new Thread(()-> {
			for (; ;) {
				eventQueue.offer(new EventQueue.Event());
			}
		}, "Producer").start();

		new Thread(()->{
			for (; ; ) {
				eventQueue.take();
				try {
					TimeUnit.MILLISECONDS.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "Consumer").start();
	}
}

//result
Consumer:the queue is empty.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@49068e0c is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@7eaac23c is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@2ae281ba is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@14b5cfee is handled.
Producer:the new event is submitted.

wait 和 notify方法详解

public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
  • wait 方法的三个重载方法都将调用wait(long timeout)这个方法,wait()方法等价于wait(0),0 代表着永不超时
  • Objectwait(long timeout)方法会导致当前线程进入阻塞,直到有其他线程调用了Objectnotify或者notifyAll方法才能将其唤醒,或者阻塞时间到达了timeout而自动唤醒
  • wait方法必须拥有该对象的monitor,也就是wait方法必须在同步方法中使用
  • 当前线程执行了该对象的wait方法之后,将会放弃对该monitor的所有权并且进入与该对象关联的wait set 中,也就是说一旦线程执行了某个Objectwait方法之后,它将会释放对该对象monitor的所有权,其他线程也会继续争抢该monitor的所有权
public final native void notify();
  • 唤醒单个正在执行对象wait方法的线程
  • 如果有某个线程由于执行该对象的wait方法而进入阻塞则会被唤醒,如果没有则会忽略
  • 被唤醒的线程需要重新获取对该对象所关联monitorlock才能继续执行

wait 和 notify的注意事项

  • wait方法是可中断方法,这也就意味着,当前线程一旦调用了wait方法进入阻塞状态,其他线程是可以使用interrupt方法将其打断的;可中断方法被打断后会收到中断异常InterruptedException,同时interrupt标识也会被擦除。
  • 线程执行了某个对象的wait方法以后,会加入与之对应的wait set中,每一个对象的monitor都有一个与之关联的wait set
  • 当线程进入wait set之后,notify方法可以将其唤醒,也就是从wait set 中弹出,同时中断wait中的线程也会将其唤醒
  • 必须在同步方法中使用waitnotify方法,因为执行waitnotify的前提条件是必须持有同步方法的monitor的所有权,否则会抛出IllegalMonitorStateException异常
  • 同步代码的monitor必须与执行waitnotify方法的对象一致,简单地说就是用哪个对象的monitor进行同步,就只能用哪个对象进行waitnotify操作,否则也会抛出IllegalMonitorStateException异常

wait 和 sleep

从表面上看,waitsleep方法都可以使当前线程进入阻塞状态,但是两者之间存在着本质的区别。
相似之处:

  • waitsleep方法都可以使线程进入阻塞状态
  • waitsleep方法均是可中断方法,被中断后都会收到中断异常
    区别:
  • waitObject的方法,而sleepThread特有的方法
  • wait 方法的执行必须在同步方法中进行,而sleep则不需要
  • 线程在同步方法中执行sleep方法时,并不会释放monitor的锁,而wait方法则会释放monitor的锁
  • sleep方法短暂休眠之后会主动退出阻塞,而wait方法(没有指定wait时间)则需要被其他线程中断后才能退出阻塞

多线程间通信

notifyAll方法
notify方法每次只能唤醒其中的一个线程,而notifyAll可以同时唤醒全部的阻塞线程。
被唤醒的线程仍需要继续争夺monitor的锁
线程休息室 wait set
线程调用了某个对象的wait方法之后都会被加入与该对象monitor关联的wait set中,并且释放monitor的所有权
另一个线程调用notify方法之后,其中一个线程会从wait set中弹出
另一个线程调用notifyAll方法之后,其中所有线程都会从wait set 中弹出

自定义显式锁 BooleanLock

synchronized关键字的缺陷
synchronized关键字提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候会被阻塞,而这种阻塞有两个明显的缺陷:
第一:无法控制阻塞时长
第二:阻塞不可被中断

package com.example.demo.thread;

import java.util.List;
import java.util.concurrent.TimeoutException;

public interface Lock {
	/**
	 * 永远阻塞,除非获取到了锁,可被中断,抛出 InterruptedException
	 */
	void lock() throws InterruptedException;

	/**
	 * 除了可中断之外,增加对应的超时功能
	 * @param mills 超时时间
	 * @throws InterruptedException 中断异常
	 * @throws TimeoutException 超时异常
	 */
	void lock(long mills) throws InterruptedException, TimeoutException;

	/**
	 * 进行锁的释放
	 */
	void unlock();

	/**
	 * 获取当前有哪些线程被阻塞
	 * @return 当前被阻塞的线程列表
	 */
	List<Thread> getBlockedThreads();
}
package com.example.demo.thread;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

public class BooleanLock implements Lock {
	/**
	 * 当前拥有锁的线程
	 */
	private Thread currentThread;  //当前拥有锁的线程

	/**
	 * boolean 开关
	 * {@code false} 代表当前锁没有被任何线程获得或者已经释放
	 * {@code true} 代表该锁已被某个线程获得,该线程就是currentThread
	 */
	private boolean locked = false;

	/**
	 * 存储哪些线程在获取当前线程时进入了阻塞状态
	 */
	private final List<Thread> blockedList = new ArrayList<>();

	/**
	 * 永远阻塞,除非获取到了锁
	 * @throws InterruptedException 中断异常
	 */
	@Override
	public void lock() throws InterruptedException {
		synchronized (this) {
			Thread thread = Thread.currentThread();
			while (locked) {
				blockedList.add(thread);
				this.wait();
			}
			blockedList.remove(thread);
			this.locked = true;
			this.currentThread = thread;
		}
	}

	/**
	 * 除了可中断之外,增加对应的超时功能
	 * @param mills 超时时间
	 * @throws InterruptedException 中断异常
	 * @throws TimeoutException 超时异常
	 */
	@Override
	public void lock(long mills) throws InterruptedException, TimeoutException {
		synchronized (this) {
			Thread thread = Thread.currentThread();
			if (mills <= 0) {
				this.lock();
			} else {
				long remainingMills = mills;
				long endMills = System.currentTimeMillis() + remainingMills;
				while (locked) {
					if (remainingMills <= 0)
						throw new TimeoutException("can not get the lock during " + mills);
					if (!blockedList.contains(thread)) {
						blockedList.add(thread);
					}
					this.wait(remainingMills);
					remainingMills = endMills - System.currentTimeMillis();
				}
				blockedList.remove(thread);
				this.locked = true;
				this.currentThread = thread;
			}
		}
	}

	/**
	 * 进行锁的释放
	 */
	@Override
	public void unlock() {
		synchronized (this) {
			if (currentThread == Thread.currentThread()) {
				this.locked = false;
				Optional.of(Thread.currentThread().getName() + " release the lock.")
						.ifPresent(System.out::println);
				this.notifyAll();
			}
		}
	}

	/**
	 * 获取当前有哪些线程被阻塞
	 * @return 当前被阻塞的线程列表
	 */
	@Override
	public List<Thread> getBlockedThreads() {
		return Collections.unmodifiableList(blockedList);
	}
}

package com.example.demo.thread;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class BooleanLockTest {
	private final Lock lock = new BooleanLock();

	public void syncMethod() {
		try {
			lock.lock();
			int randomInt = ThreadLocalRandom.current().nextInt(10);
			System.out.println(Thread.currentThread() + " get the lock.");
			TimeUnit.SECONDS.sleep(randomInt);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) {
		BooleanLockTest blt = new BooleanLockTest();
		IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);
	}
}

//result
Thread[Thread-0,5,main] get the lock.
Thread-0 release the lock.
Thread[Thread-9,5,main] get the lock.
Thread-9 release the lock.
Thread[Thread-2,5,main] get the lock.
Thread-2 release the lock.
Thread[Thread-8,5,main] get the lock.
Thread-8 release the lock.
Thread[Thread-3,5,main] get the lock.
Thread-3 release the lock.
Thread[Thread-5,5,main] get the lock.
Thread-5 release the lock.
Thread[Thread-6,5,main] get the lock.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值