3-初识-多线程之间通讯

学习内容来源于蚂蚁课堂,感谢蚂蚁课堂大佬的指导,本文仅供学习记录使用,如有不适,请联系作者。

一、什么是多线程之间的通讯

多个线程对同一个资源(共享资源),每个线程对共享资源做的动作不同,操作不同。
在这里插入图片描述

1.1产生问题的原因:

  1. 举例:生产者写操作,username = 仓小姐,sex=女,消费者线程读操作,读到username = 仓小姐,sex未读取时,生产者线程写操作 username = 加老师,sex = 男,此时读线程读到sex = 男,打印 仓小姐,男。
  2. 举例:根据java内存模型,读线程本地私有内存没有及时与主内存同步,造成数据混乱。

1.2解决思路

写线程与读线程要进行同步,读的时候不能写,写的时候不能读,可见性不具备原子性(加Volatile关键字不管用)线程不安全,因为你即使具备了及时可见,但是你无法改变例1的情况,无法改变共享资源的某个属性被变更这一事实。

/**
*
*问题代码演示
*/
public class Demo01 {
	public static void main(String[] args) {
		Res res = new Res();
		Out out = new Out(res);
		Input input = new Input(res);
		out.start();
		input.start();
	}
}
/**
 * 共享资源
 */
class Res {
	public String userName;
	public String sex;
	
}

/**
 * 写操作
 */
class Out extends Thread{
	Res res;
	
	public Out(Res res) {
		this.res = res;
	}
	
	@Override
	public void run() {
		//写操作
		int count = 0;
		while (true) {
			if (count == 0) {
				res.userName = "苍小姐";
				res.sex = "女";
			}else {
				res.userName = "加老师";
				res.sex = "男";
			}
			// 计算是奇数还是偶数
			count = (count + 1) % 2;
		}
	}
}

/**
 * 读操作
 */
class Input extends Thread{
	Res res;
	
	public Input(Res res) {
		this.res = res;
	}
	
	@Override
	public void run() {
		while (true) {
			System.out.println(res.userName + "," + res.sex);
		}
	}
}
...省略
苍小姐,女
加老师,男
苍小姐,男
加老师,女
加老师,男
加老师,女
加老师,...省略

二、wait()与notify()

/**
 * 
 * 做到消费一个生产一个,生产一个消费一个
 * 
 * wait()让当前线程产品能够运行状态变为休眠状态
 * notify()让当前线程从休眠状态变为准备状态
 * 同步状态才能使用,并且是同一个锁资源
 *
 *
 *	wait()用于同步中,可以释放锁的资源,需要notify唤醒
 *	sleep()不会释放锁的资源 ,只要到时间就会唤醒
 */
public class Demo02 {
	public static void main(String[] args) {
		Res2 res = new Res2();
		Out2 out = new Out2(res);
		Input2 input = new Input2(res);
		out.start();
		input.start();
	}
}

class Res2 {
	public volatile String userName;
	public volatile String sex;
	//为true写操作等待,为flase读操作等待
	public volatile boolean flag = false;
	
}

class Out2 extends Thread{
	Res2 res;
	
	public Out2(Res2 res) {
		this.res = res;
	}
	
	@Override
	public void run() {
		//写操作
		int count = 0;
		while (true) {
			synchronized (res) {
				System.out.println("写线程进入");
				if (res.flag) {
					try {
						res.wait(); //让当前线程产品能够运行状态变为休眠状态,并释放锁资源
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				if (count == 0) {
					res.userName = "苍老师";
					res.sex = "女";
				}else {
					res.userName = "崔嘉文";
					res.sex = "男";
				}
				// 计算是奇数还是偶数
				count = (count + 1) % 2;
				res.flag = true;
				System.out.println("写线程结束");
				//唤醒读操作
				res.notify();
			}
		}
	}
}

class Input2 extends Thread{
	Res2 res;
	
	public Input2(Res2 res) {
		this.res = res;
	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (res) {
				System.out.println("读线程进入");
				if (!res.flag) {
					try {
						res.wait(); //让当前线程产品能够运行状态变为休眠状态,并释放锁资源
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println(res.userName + "," + res.sex);
				res.flag = false;
				System.out.println("读线程结束");
				//唤醒写操作
				res.notify();
			}
		}
	}
}
//输出
...省略
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,...省略

此代码有不妥之处或提供更好的例证,欢迎大家反馈!

三、JDK1.5 Lock锁

lock锁 jdk1.5并发包 保证线程安全问题 属于手动挡,手动上锁,手动释放锁 灵活性高。
synchronized锁 从代码开始就上锁 代码结束就释放锁 内置锁,自动化,效率低,扩展不高,不能自定义。

public class Demo03 {
	public static void main(String[] args) {
		Res3 res = new Res3();
		//用于线程间通讯
		Condition condition = res.lock.newCondition();
		Out3 out = new Out3(res,condition);
		Input3 input = new Input3(res,condition);
		out.start();
		input.start();
	}
}

class Res3 {
	public volatile String userName;
	public volatile String sex;
	public volatile boolean flag = false;
	//重入锁
	Lock lock = new ReentrantLock();
}

class Out3 extends Thread {
	Res3 res;

	Condition condition;
	public Out3(Res3 res,Condition condition) {
		this.res = res;
		this.condition = condition;
	}

	@Override
	public void run() {
		// 写操作
		int count = 0;
		while (true) {
			//上锁
			res.lock.lock();
			try {
				if (res.flag) {
					//只能这样写,锁对象.await()只能用于同步synchronized中
					//是本线程休眠,并释放锁
					condition.await(); 
				}
				if (count == 0) {
					res.userName = "苍小姐";
					res.sex = "女";
				} else {
					res.userName = "加老师";
					res.sex = "男";
				}
				// 计算是奇数还是偶数
				count = (count + 1) % 2;
				res.flag = true;
				//唤醒
				condition.signal();
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//释放锁
				res.lock.unlock();
			}
		}
	}
}

class Input3 extends Thread {
	Res3 res;
	Condition condition;
	public Input3(Res3 res,Condition condition) {
		this.res = res;
		this.condition = condition;
	}

	@Override
	public void run() {
		while (true) {
			res.lock.lock();
			try {
				if (!res.flag) {
					condition.await();
				}
				System.out.println(res.userName + "," + res.sex);
				res.flag = false;
				condition.signal();
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				res.lock.unlock();
			}
		}
	}
}
//输出
...省略
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,...省略

3.1 Lock锁与synchronized锁的区别

  1. lock接口可以尝试非阻塞地获取锁,当前线程尝试获取锁。如果这一时刻没有被其他线程获取到,则成功获取并持有锁。
  2. lock接口能被中断的获取锁,与synchronized不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时释放锁。
  3. lock接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。

后续深入讨论

四、怎么停止线程

  1. stop 不建议,方法过期,强制中断,不可恢复。
  2. 建议从代码执行完毕角度考虑。
  3. interrupt(),抛出异常,在try{}catch{}中做手脚。
public class Demo04 {
	public static void main(String[] args) throws InterruptedException {
		StopThread thread = new StopThread();
		thread.start();
		System.out.println("主线程");
		for (int i = 0; i < 30; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("主线程..." + i);
				if (i == 6) {
					thread.stopThread(false);
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

class StopThread extends Thread {
	private volatile boolean flag = true;
	@Override
	public void run() {
		System.out.println("子线程开始");
		while (flag) {
			
		}
		System.out.println("子线程结束");
	}
	
	public void stopThread(boolean flag) {
		System.out.println("调用了子线程停止方法");
		this.flag = flag;
		System.out.println("将flag的值修改为" + flag);
	}
}
...省略
主线程...3
主线程...4
主线程...5
主线程...6
调用了子线程停止方法
将flag的值修改为false
子线程结束
主线程...7
主线程...8
...省略

五、ThreadLock

本地线程,为每一个线程提供一个本地局部变量。
原理:Map<当前线程,value>

public class Demo05 {
	public static void main(String[] args) {
		ResNumber resNumber = new ResNumber();
		ThreadLockDemo t1 = new ThreadLockDemo(resNumber);
		ThreadLockDemo t2 = new ThreadLockDemo(resNumber);
		ThreadLockDemo t3 = new ThreadLockDemo(resNumber);
		t1.start();
		t2.start();
		t3.start();
	}
}

class ResNumber{
	public int count = 0;
	
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {	
		protected Integer initialValue() {
			return 0;		
		};
	};
	
	public String getNumber() {
		//获取值
		count = threadLocal.get() + 1;
		//赋值
		threadLocal.set(count);
		return count + "" ;
	}
}

class ThreadLockDemo extends Thread{
	private ResNumber resNumber;
	
	public ThreadLockDemo(ResNumber resNumber) {
		this.resNumber = resNumber;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(getName() + "---" + resNumber.getNumber());
		}
	}
}
Thread-0---1
Thread-0---2
Thread-0---3
Thread-1---1
Thread-1---2
Thread-1---3
Thread-2---1
Thread-2---2
Thread-2---3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值