java架构学习——3. 多线程之间实现通讯

本篇博文主要包含:

  • 多线程之间通讯
  • synchronized保证多线程安全
  • wait、notify、notifyAll方法在synchronized中的用法及解决重复消费问题
  • wait与sleep的区别
  • Lock锁(手动)
  • Lock 接口与 synchronized 关键字的区别
  • Lock锁中使用Condition类控制线程的等待与唤醒
  • 如何停止线程
  1. 什么是多线程之间通讯?
    多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。例如:第一个线程写入(input)用户,另一个线程取读取(out)用户.实现读一个,写一个操作。
    代码演示:
/**
 * 共享资源源实体类
 *
 */
class People{
	String name;
	String sex;
}

/**
 * 写线程类
 *
 */
class OutputThread extends Thread{
	People people;
	public OutputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		int count = 0 ;
		while(true) {
				if(count==0) {
					people.name = "方向";
					people.sex = "女";
				}else {
					people.name = "目标";
					people.sex = "男";
				}
			count = (count+1)%2;
		}
	}
}

/**
 * 读线程类
 *
 */
class InputThread extends Thread{
	People people;
	public InputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		while(true) {
			System.out.println(people.name+",  "+people.sex);
		}
	}
}

public class OutInputThread {
	public static void main(String[] args) {
		//共享people对象
		People people = new People();
		
		OutputThread out = new OutputThread(people);
		InputThread input = new InputThread(people);
		
		out.start();
		input.start();
	}
}

运行结果:
在这里插入图片描述
由运行结果可以看出,数据发生错乱,造成线程安全问题。

  1. 加上synchronized关键字
/**
 * 共享资源源实体类
 *
 */
class People{
	String name;
	String sex;
}

/**
 * 写线程类
 *
 */
class OutputThread extends Thread{
	People people;
	public OutputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		int count = 0 ;
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				if(count==0) {
					people.name = "方向";
					people.sex = "女";
				}else {
					people.name = "目标";
					people.sex = "男";
				}
			}
			count = (count+1)%2;
		}
	}
}

/**
 * 读线程类
 *
 */
class InputThread extends Thread{
	People people;
	public InputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				System.out.println(people.name+",  "+people.sex);
			}
		}
	}
}

public class OutInputThread {
	public static void main(String[] args) {
		//共享people对象
		People people = new People();
		
		OutputThread out = new OutputThread(people);
		InputThread input = new InputThread(people);
		
		out.start();
		input.start();
	}
}

运行结果:
在这里插入图片描述
线程安全问题解决了,但是消费者在重复消费。如何解决这个问题呢?我们使用wait()、notify()处理这个问题。

  1. wait()、notify、notifyAll()方法
    wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
    这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
    (1)如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
    (2)如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
    (3)如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
    注意:一定要在线程同步中使用,并且是同一个锁的资源
    代码演示:
/**
 * 共享资源源实体类
 *
 */
class PeopleWaitNotify{
	String name;
	String sex;
	//
	Boolean flag = false;
}

/**
 * 写线程类
 *
 */
class OutputWaitNotifyThread extends Thread{
	PeopleWaitNotify people;
	public OutputWaitNotifyThread(PeopleWaitNotify people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		int count = 0 ;
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				if(people.flag) {
					try {
						// 当前线程变为等待(从运行状态变为等待状态),但是可以释放锁
						people.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if(count==0) {
					people.name = "方向";
					people.sex = "女";
				}else {
					people.name = "目标";
					people.sex = "男";
				}
				//唤醒其他等待线程(从等待状态变为运行状态)
				people.notify();
				people.flag= true;
			}
			count = (count+1)%2;
			
		}
	}
}

/**
 * 读线程类
 *
 */
class InputWaitNotifyThread extends Thread{
	PeopleWaitNotify people;
	public InputWaitNotifyThread(PeopleWaitNotify people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				if(!people.flag) {
					try {
						people.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(people.name+",  "+people.sex);
				people.notify();
				people.flag = false;
			}
		}
	}
}

public class WaitNotifyOutInputThread {
	public static void main(String[] args) {
		//共享people对象
		PeopleWaitNotify people = new PeopleWaitNotify();
		
		OutputWaitNotifyThread out = new OutputWaitNotifyThread(people);
		InputWaitNotifyThread input = new InputWaitNotifyThread(people);
		
		out.start();
		input.start();
	}
}

运行结果:
在这里插入图片描述
使用wait()、notify()实现消费者重复消费问题,即生产者线程消费一个,消费者线程立马消费; 生产者没有任何生产,消费者不能读;消费者,没有消费完,生产者不能在继续生产。

注意:wait()、notify()及notifyAll()是用在synchronized代码块内的

  1. wait与sleep的区别
    (1) 对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
    sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
    (2)在调用sleep()方法的过程中,线程不会释放对象锁
    而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
  2. Lock锁
    在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

(1)lock的写法:

Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.ublock();
}

(2)Lock 接口与 synchronized 关键字的区别

  • Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
  • Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
  • Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
  • synchronized的缺点:效率低、扩展性不好、不能自动化
    代码演示:
/**
 * 共享资源源实体类
 *
 */
class People{
	String name;
	String sex;
}

/**
 * 写线程类
 *
 */
class OutputThread extends Thread{
	People people;
	public OutputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		int count = 0 ;
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				if(count==0) {
					people.name = "方向";
					people.sex = "女";
				}else {
					people.name = "目标";
					people.sex = "男";
				}
			}
			count = (count+1)%2;
		}
	}
}

/**
 * 读线程类
 *
 */
class InputThread extends Thread{
	People people;
	public InputThread(People people) {
		this.people = people;
	}
	
	@Override
	public void run() {
		while(true) {
			//解决线程安全问题加上synchronized
			synchronized(people) {
				System.out.println(people.name+",  "+people.sex);
			}
		}
	}
}

public class OutInputThread {
	public static void main(String[] args) {
		//共享people对象
		People people = new People();
		
		OutputThread out = new OutputThread(people);
		InputThread input = new InputThread(people);
		
		out.start();
		input.start();
	}
}
  1. Condition用法
    在synchronized中使用wait()、notify()控制线程的等待与唤醒,而在Lock锁中使用Condition。
    Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能。

代码演示:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 共享资源源实体类
 *
 */
class PeopleLock{
	String name;
	String sex;
	boolean flag = false;
	//在共享资源里定义lock锁
	Lock lock = new ReentrantLock();
}

/**
 * 写线程类
 *
 */
class OutputLockThread extends Thread{
	PeopleLock people;
	Condition newCondition;
	
	public OutputLockThread(PeopleLock people,Condition newCondition) {
		this.people = people;
		this.newCondition = newCondition;
	}
	
	@Override
	public void run() {
		int count = 0 ;
		while(true) {
			try {
				//开启锁
				people.lock.lock();
				
				if(people.flag) {
					try {
						// 当前线程变为等待(从运行状态变为等待状态),但是可以释放锁
						newCondition.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if(count==0) {
					people.name = "方向";
					people.sex = "女";
				}else {
					people.name = "目标";
					people.sex = "男";
				}
				count = (count+1)%2;
				people.flag= true;
				//唤醒其他等待线程(从等待状态变为运行状态)
				newCondition.signal();
				
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//关闭锁
				people.lock.unlock();
			}
		}
	}
}

/**
 * 读线程类
 *
 */
class InputLockThread extends Thread{
	PeopleLock people;
	Condition newCondition;
	
	public InputLockThread(PeopleLock people,Condition newCondition) {
		this.people = people;
		this.newCondition = newCondition;
	}
	
	@Override
	public void run() {
		while(true) {
			try {
				//开启锁
				people.lock.lock();
				if(!people.flag) {
					try {
						// 当前线程变为等待(从运行状态变为等待状态),但是可以释放锁
						newCondition.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(people.name+",  "+people.sex);
				people.flag= false;
				//唤醒其他等待线程(从等待状态变为运行状态)
				newCondition.signal();
				
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//关闭锁
				people.lock.unlock();
			}
		}
	}
}

public class LockThread {
	public static void main(String[] args) {
		//共享people对象
		PeopleLock people = new PeopleLock();
		//同一个newCondition
		Condition newCondition = people.lock.newCondition();
		
		OutputLockThread out = new OutputLockThread(people,newCondition);
		InputLockThread input = new InputLockThread(people,newCondition);
		
		out.start();
		input.start();
	}
}

运行结果:
在这里插入图片描述
使用Lock锁和Condition代替synchronized和wait()、notify()。

  1. 如何停止线程

(1) 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

代码演示:

class StopThread extends Thread{
	//使用volatile关键字保证原子可见性
	private volatile boolean flag = true;
	
	@Override
	public void run() {
		System.out.println("子线程运行begin。。。。。");
		while(flag) {
			
		}
		System.out.println("子线程运行end。。。。。");
	}
	
	public void stopThread() {
		this.flag = false;
	}
}


public class StopThreadDemo {
	public static void main(String[] args) {
		StopThread stopThread = new StopThread();
		stopThread.start();
		
		for(int i=0; i<5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);
			if(i==3) {
				stopThread.stopThread();
			}
		}
	}
}

运行结果:
在这里插入图片描述

(2) 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果),不可恢复。

代码演示:

class StopThread extends Thread{
	//使用volatile关键字保证原子可见性
	private volatile boolean flag = true;
	
	@Override
	public void run() {
		System.out.println("子线程运行begin。。。。。");
		while(flag) {
			
		}
		System.out.println("子线程运行end。。。。。");
	}
}


public class StopThreadDemo {
	public static void main(String[] args) {
		StopThread stopThread = new StopThread();
		stopThread.start();
		
		for(int i=0; i<5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);
			if(i==3) {
				stopThread.stop();
			}
		}
	}
}

运行结果:
在这里插入图片描述
(3) 使用interrupt方法中断线程

代码演示:

class StopThread extends Thread{
	//使用volatile关键字保证原子可见性
	private volatile boolean flag = true;
	
	@Override
	public void run() {
		System.out.println("子线程运行begin。。。。。");
		while(flag) {
			try {
				wait();
			} catch (Exception e) {
				stopThread();
				e.printStackTrace();
			}
		}
		System.out.println("子线程运行end。。。。。");
	}
	
	public void stopThread() {
		this.flag = false;
	}
}


public class StopThreadDemo {
	public static void main(String[] args) {
		StopThread stopThread = new StopThread();
		stopThread.start();
		
		for(int i=0; i<5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);
			if(i==3) {
				//当前等待的线程直接抛出异常
				stopThread.interrupt();
			}
		}
	}
}

运行结果:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值