我们在开发多线程程序的时候,往往不会只存在一个独立的线程,相反大多数情况下是需要多个线程之间进行协同工作的,如何在多个线程之间进行通信,是本章学习的重点。另外,本章的最后部分将会分析synchronized关键字的缺陷,我们手动实现了一个显式锁(BooleanLock)可以解决synchronized所不具备的功能,其中也需要用到线程间通信的知识。
同步阻塞与异步非阻塞
异步非阻塞消息处理
单线程间通讯
初识wait和notify
代码样例:
import java.util.LinkedList ;
import static java.lang.Thread.currentThread ;
public class EventQueue {
private final int max ;
static class Event {
int i ;
public Event(int i){
this.i = i ;
}
}
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.i + " is handled . ");
return event ;
}
}
private void console(String message){
System.out.printf("%s:%s \n", currentThread().getName() , message);
}
}
import java.util.concurrent.TimeUnit;
public class EventClient {
public static void main(String[] args) {
final EventQueue eventQueue = new EventQueue() ;
new Thread(() -> {
for (int i = 0 ; i < 100000; i++){
eventQueue.offer(new EventQueue.Event(i));
}
},"Producer" ).start();
new Thread(() -> {
for (;;){
eventQueue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Consumer " ).start(); ;
}
}
运行截图:
wait和notify方法详解
wait和notify方法并不是Thread特有的方法,而是Object中的方法。也就是说JDK中每一个类都拥有这两个方法。
先看wait的三个重载方法:
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
说明:
1.wait方法这三个重载方法都将调用wait(long timeout)方法 , wait()方法 等价于 wait(0), 0 代表永不超时。
2.Object的wait(long timeout)方法会导致当前线程进入阻塞,知道有其他线程调用了Object的nofify或者notifyAll方法才能将其唤醒,或者阻塞时间到达了timeout时间而自动唤醒。
3.wait方法必须拥有该对象的monitor,也就是waite方法必须在同步方法中使用。
4.当前线程执行了该对象的wai方法之后,将会放弃对该monitor的所有权,并进进入到与该对象关联的wait set中,也就是说一旦线程执行了某个object的wait方法之后,他就会释放对该对象的monitor的所有权, 其他线程也会有机会继续争抢该monitor的所有权。
notify解析
public final native void notify();
唤醒单个正在执行该对象的wait方法的线程
如果有某个线程由于执行该对象的wait方法而进入阻塞则会被唤醒,如果没有则会忽略。
被唤醒的线程需要重新获取对该对象所关联monitor的lock才能继续执行
关于wait和notify的注意事项
1.wait方法是可中断方法,一旦调用了wait方法案进入了阻塞状态, 其他线程可以使用interrupt方法将其打断。
可中断方法被打断后会受到中断异常InterruptedException,同时interrupt标识也会被擦除。
2.线程执行了某个对象的wait方法之后,会加入与之对应的wait set中, 每一个对象的monitor都会有一个与之关联的wait set
3.当前程进入wait set之后, notify方法可以将其进行唤醒, 也就是从wait set中弹出,同时,中断wait中的线程,也就将其唤醒。
4.必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是必须持有同步方法monitor的所有权。
wait和sleep
1.wait和sleep方法都可以使线程进入阻塞状态。
2.wait和sleep方法都是可中断方法,被中断之后,都会受到中断异常
3.wait是Object的方法, sleep是Thread中的特有方法
4.wait方法的执行必须要同步方法中进行,则sleep不需要
5.线程在同步方法中执行sleep方法是,并不释放monitor锁,而wait方法则会释放
6.sleep方法短暂休眠后会主动退出阻塞,而wait方法(没有指定wait时间)则需要被其他线程中断之后才能退出阻塞
多线程间通讯
生产者和消费者
1.notifyAll方法
多线程间通讯需要用到Object的notifyAll方法,该方法与notify比较相似,都可以唤醒由于调用了wait方法而阻塞的线程,但是notify方法只能唤醒其中的一个线程,而notifyAll方法则可以同时唤醒全部的阻塞线程,同时被唤醒的线程仍然需要继续争抢monitor的锁。
代码样例:
package com.zl.step5.eventQueue2;
import java.util.LinkedList;
import static java.lang.Thread.currentThread;
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){
while(eventQueue.size() >= max ){
try{
console("the queue is full ...");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
console("the new event is submitted :" +event);
eventQueue.addLast(event);
eventQueue.notifyAll();
}
}
public Event take(){
synchronized (eventQueue){
while(eventQueue.isEmpty()){
try {
console("the queue is empty . ");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Event event = eventQueue.removeFirst();
this.eventQueue.notifyAll();
console("the event " + event + " is handled . ");
return event ;
}
}
private void console(String message){
System.out.printf("%s:%s \n", currentThread().getName() , message);
}
}
package com.zl.step5.eventQueue2;
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-1" ).start();
new Thread(() -> {
for (;;){
eventQueue.offer(new EventQueue.Event());
}
},"Producer-2" ).start();
new Thread(() -> {
for (;;){
eventQueue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Consumer " ).start(); ;
}
}
线程休息室wait set
线程调用了某个对象的wait方法之后,都会被加入该对象的monitor关联的wait set中, 并且释放monitor的所有权
nofify方法:
notifyAll方法
自定义显示锁BooleanLock
synchronized关键字的缺陷
synchronized关键字给提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞,
有两个明显的缺陷:
1.无法控制阻塞时长
2.阻塞不可被中断
测试代码:
package com.zl.step5;
import java.util.concurrent.TimeUnit;
public class SynchronizedDefect {
public synchronized void syncMethod(){
try {
System.out.printf("Thread [ %s ] is running .....\n ", Thread.currentThread().getName());
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDefect defect = new SynchronizedDefect();
Thread t1 = new Thread(defect::syncMethod,"T1");
t1.start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(defect::syncMethod,"T2");
t2.start();
}
}
package com.zl.step5;
import java.util.concurrent.TimeUnit;
public class SynchronizedDefect2 {
public synchronized void syncMethod(){
try {
System.out.printf("Thread [ %s ] is running .....\n ", Thread.currentThread().getName());
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDefect2 defect = new SynchronizedDefect2();
Thread t1 = new Thread(defect::syncMethod,"T1");
t1.start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(defect::syncMethod,"T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(2);
t2.interrupt();
System.out.printf("t2 interrupted : %s \n ", t2.isInterrupted());
System.out.printf("t2 state : %s \n ", t2.getState());
}
}
t2 在竞争资源的时候,处于阻塞状态 ,是无法被打断的。
显示锁BooleanLock
使其具备synchronized关键字所有的功能,同时又具备可中断和lock超时的功能
1.定义Lock接口
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* 1.lock()方法永远阻塞,除非获取到了锁,这一点和synchronized非常类似
* 但是该方法是可以被中断的,中断的时候,抛出InterruptedException
* 2.lock(long mills) 方法除了可以被中断以外,还增加了对应的超时功能
* 3.unlock() 方法可用来释放锁
* 4.getBlockedThreads() 用于获取当前有哪些线程被阻塞
*
*
*/
public interface Lock {
void lock() throws InterruptedException;
void lock(long mills) throws InterruptedException, TimeoutException ;
void unlock();
List<Thread> getBlockedThreads();
}
* 1.lock()方法永远阻塞,除非获取到了锁,这一点和synchronized非常类似
* 但是该方法是可以被中断的,中断的时候,抛出InterruptedException
* 2.lock(long mills) 方法除了可以被中断以外,还增加了对应的超时功能
* 3.unlock() 方法可用来释放锁
* 4.getBlockedThreads() 用于获取当前有哪些线程被阻塞
2.实现BooleanLock
BooleanLock是Lock的一个boolean实现,通过控制一个Boolean变量的开关来决定是否允许当前的线程获取该锁。
代码:
package com.zl.step5.booleanlock;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* 1.lock()方法永远阻塞,除非获取到了锁,这一点和synchronized非常类似
* 但是该方法是可以被中断的,中断的时候,抛出InterruptedException
* 2.lock(long mills) 方法除了可以被中断以外,还增加了对应的超时功能
* 3.unlock() 方法可用来释放锁
* 4.getBlockedThreads() 用于获取当前有哪些线程被阻塞
*
*
*/
public interface Lock {
void lock() ;
void lock(long mills) throws InterruptedException, TimeoutException ;
void unlock();
List<Thread> getBlockedThreads();
}
package com.zl.step5.booleanlock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.currentThread;
public class BooleanLock implements Lock {
// 当前拥有锁的线程
private Thread lockThread ;
// ture 该锁已经被某个线程所获得
// false代表该锁没有被任何线程获得或者已经被释放
private boolean locked = false ;
// 用来存储哪些线程在获取当前线程时计入阻塞状态
private final List<Thread> blockedList = new ArrayList<>();
@Override
public synchronized void lock() {
// System.out.println("lock 当前线程:"+currentThread() );
// System.out.println("lock 持有锁线程:"+lockThread );
// 使用lock方法使用同步代码块的方式进行方法同步
synchronized (this){
// 如果当前锁已经被某个线程所获得,则该线程加入阻塞队列
// 并且使当前线程wait释放对this monitor的所有权
while (locked){
final Thread tempThread = currentThread();
try{
if(!blockedList.contains(currentThread())){
blockedList.add(currentThread());
}
this.wait();
}catch (InterruptedException e){
blockedList.remove(tempThread) ;
}
}
// 如果当前锁没有被其他线程所获得,则该线程尝试从阻塞队列中删除自己
blockedList.remove(currentThread());
// locked 开关指定为true
this.locked = true ;
// 记录获取锁的线程
this.lockThread = currentThread() ;
}
}
@Override
public void lock(long mills) throws InterruptedException, TimeoutException {
synchronized (this){
// 如果mills 不合法, 则默认调用lock方法 ,当然也可以抛出参数非法异常
if(mills <= 0){
this.lock();
}else{
long remainingMills = mills ;
long endMills = currentTimeMillis() + remainingMills ;
while(locked){
//如果remainingMills 小于等于0 ,
// 则意味着当前线程被其他线程唤醒或者在指定的wait时间到了之后,
// 还没有获得锁,则抛出超时异常
if(remainingMills <= 0){
throw new TimeoutException("can not get the lock during " + mills) ;
}
if(!blockedList.contains(currentThread())){
blockedList.add(currentThread());
}
// 调用wait
this.wait(remainingMills);
// 重新计算remainingMills 时间,因为该线程有可能会被唤醒
remainingMills = endMills - currentTimeMillis() ;
}
blockedList.remove(currentThread()) ;
this.locked = true ;
this.lockThread = currentThread() ;
}
}
}
@Override
public void unlock() {
// System.out.println("unlock当前线程:"+currentThread() );
// System.out.println("unlock持有锁线程:"+lockThread );
synchronized (this){
//解锁, 那个线程加的锁,只能由该线程进行解锁
if (lockThread == currentThread()){
this.locked = false;
Optional.of(currentThread().getName()+" release the lock." ).ifPresent(System.out::println);
this.notifyAll();
}else{
System.out.println(currentThread() + " unlock fail ... ");
}
}
}
@Override
public List<Thread> getBlockedThreads() {
return Collections.unmodifiableList(blockedList);
}
}
package com.zl.step5.booleanlock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.ThreadLocalRandom.current;
public class BooleanLockTest {
private final Lock lock = new BooleanLock();
public void syncMethod() {
try {
lock.lock(10 * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
int randomInt = current().nextInt(3) ;
System.out.println(currentThread() + " get the lock , is running .... ");
TimeUnit.SECONDS.sleep(randomInt);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("mian Thread : " + Thread.currentThread());
BooleanLockTest blt = new BooleanLockTest();
IntStream.range(0,10)
.mapToObj(i -> new Thread(blt::syncMethod))
.forEach(Thread::start);
// new Thread(blt::syncMethod,"T1").start();
//
// TimeUnit.MILLISECONDS.sleep(2);
//
// Thread t2 = new Thread(blt::syncMethod,"T2") ;
//
// t2.start();;
// TimeUnit.MILLISECONDS.sleep(2);
// t2.interrupt();
}
}
本文整理来源于:
《Java高并发编程详解:多线程与架构设计》 --汪文君