IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java个人学习之旅(第十九天) -> 正文阅读

[Java知识库]Java个人学习之旅(第十九天)

对线程的补充

1. 线程死锁:

  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁

  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

死锁代码示例:

public class DeadLock implements Runnable{

	int flag = 1;
	//定义两个锁对象
	Object obj1 = new Object();
	Object obj2 = new Object();
	
	@Override
	public void run() {
		if (flag == 1) {
			//锁obj1
			synchronized (obj1) {
				System.out.println("obj1的外锁");
				try {
					//睡一下,增大死锁的概率
					Thread.sleep(1000);
					flag = 0;
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//在锁里嵌套锁obj2
				synchronized (obj2) {
					System.out.println("obj2的内锁");
				}
			}
		}
		//下方同理
		if (flag == 0) {
			synchronized (obj2) {
				System.out.println("obj2的外锁");
				try {
					Thread.sleep(1000);
					flag = 1;
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (obj1) {
					System.out.println("obj1的内锁");
				}
			}
		}
	}
	
	public static void main(String[] args) {
		DeadLock dl1 = new DeadLock();
		new Thread(dl1).start();
		new Thread(dl1).start();
		
	}
}

运行结果:
在这里插入图片描述
可以看到右上角的方块是亮红的,这意味着程序没有终止,但是控制台只打印了四行就没再继续打印了

注意打印的最后两行,打印了obj1的外锁之后就没有下文了,这说明obj1在打印后去锁obj2,可是obj2却在这个时候去锁obj1,这个时候两个锁都没有放开,谁都无法拿到对方的资源,死锁就是这样发生的

2. 线程的通信

线程的通信就是一个线程会利用到另一个线程处理后的数据

  1. wait()
    令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

  2. notify()

  • 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • 唤醒条件:当前线程必须对该对象有监控权(synchronized)
  1. notifyAll()
  • 唤醒正在排队等待资源的所有线程结束等待,最好用在两个以上的线程中
  • 唤醒条件:当前线程必须对该对象有监控权(synchronized)

注意:

以上三个方法都必须用在synchronized修饰的代码块中或方法中,否则会报 java.lang.IllegalMonitorStateException异常

其中很典型的就是模拟生产者和消费者

代码示例:

//生产者消费者模式
class Clerk {
	
	private int product = 0;
	
	public synchronized void addProduct() {
		//添加商品
		if (product >= 30) {
			//商品大于等于30,说明商品够多了,该线程等待消费者取走商品
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			//如果商品不够,生产者就生产商品并且唤醒消费者线程前来消费
			product++;
			System.out.println("生产者生产了第" + product + "个商品");
			notifyAll();
		}
	}
	
	public synchronized void getProduct() {
		//取走商品
		if (this.product <= 0) {
			//如果没有商品,则休眠消费者线程,等待生产者线程生产商品
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			//如果商品够,则消费者取走商品,且唤醒生产者生产商品
			System.out.println("消费者取走了第" + (product--) + "个商品");
			notifyAll();
		}
	}
}

class Producer implements Runnable{

	Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		System.out.println("生产者开始生产商品");
		while(true) {
			try {
				//随机时间生产商品
				Thread.sleep((long) (Math.random()*1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}

class Consumer implements Runnable{
	Clerk clerk;

	
	public Consumer(Clerk clerk) {
		super();
		this.clerk = clerk;
	}

	@Override
	public void run() {
		while(true) {
			try {
				//随机时间消费商品
				Thread.sleep((long) (Math.random()*1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.getProduct();
		}
	}
}

public class Test6 {
	public static void main(String[] args) {
		//售货员对象
		Clerk clerk = new Clerk();
		//生产者线程
		Thread producerThread = new Thread(new Producer(clerk));
		//消费者线程 
		Thread consumerThread = new Thread(new Consumer(clerk));
		producerThread.start();
		consumerThread.start();
	}
}

运行结果:
在这里插入图片描述
可以看到商品不断地被生成和取走

注意:
wait()方法只是暂时失去锁,程序的执行暂停,获得锁后程序会从停止处继续执行而非重新开始执行

而sleep()方法和yield()方法只会使线程延时执行,不会让线程失去锁

3. 线程的声明周期流程图

在这里插入图片描述
等待与计时等待的区别:

等待就是线程没有抢到CPU时间片无法执行的那一段时间,可能很短暂也可能很长,无法确定是多久

计时等待就是人为的给线程调用sleep()方法,有固定的等待时间

4. 一道通过LOCK改写的题目

使用线程在控制台打印"回首向来萧瑟处,归去,也无风雨也无晴"

class Shower {

	int count = 0;
	//初始化一个lock对象
	Lock lock = new ReentrantLock();
	//通过lock获取三个线程控制类对象,一个控制对象指定一个线程
	Condition c1 = lock.newCondition();
	Condition c2 = lock.newCondition();
	Condition c3 = lock.newCondition();

	public void show1() throws InterruptedException {
		for (int i = 0; i < 100; i++) {
			//加锁
			lock.lock();
			while (count != 0) {
				// wait();
				//当前线程等待
				c1.await();
			}
			Thread.sleep(20);
			System.out.println("回首向来萧瑟处");

			count = 1;
			// notifyAll();
			//唤醒打印第二句的线程
			c2.signal();
			lock.unlock();
		}
	}

	public void show2() throws InterruptedException {
		for (int i = 0; i < 100; i++) {
			lock.lock();
			while (count != 1) {
				// wait();
				c2.await();
			}
			Thread.sleep(20);
			System.out.println("归去");

			count = 2;
			// notifyAll();
			c3.signal();
			lock.unlock();
		}
	}

	public void show3() throws InterruptedException {
		for (int i = 0; i < 100; i++) {
			lock.lock();
			while (count != 2) {
				// wait();
				//当前线程等待
				c3.await();
			}
			Thread.sleep(20);
			System.out.println("也无风雨也无晴");

			count = 0;
			// notifyAll();
			//唤醒第一句的线程,循环打印
			c1.signal();
			lock.unlock();
		}
	}
}

public class Test1 {
	public static void main(String[] args) {
		Shower s = new Shower();

		new Thread() {

			@Override
			public void run() {
				try {
					s.show1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}.start();

		new Thread() {

			@Override
			public void run() {
				try {
					s.show2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}.start();

		new Thread() {

			@Override
			public void run() {
				try {
					s.show3();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}.start();
	}
}

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

可以看到,三句话循环打印了很多遍
Condition 有一个很大的优点就是可以指定线程去唤醒和睡眠,这是很节省内存的

5.三道练习题

  1. 模拟多个人通过一个山洞,这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒。随机生成10个人,同时准备过此山洞,显示每次通过山洞人的姓名。
class Cave implements Runnable {

	int count = 1;

	@Override
	public void run() {
		cross();
	}

	private synchronized void cross() {
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "通过了山洞,他是第" + (count++) + "个过山洞的");
	}
}

public class Test3 {
	public static void main(String[] args) {
		Cave c = new Cave();
		
		for(int i=1;i <= 10;i++){
			new Thread(c,i+"号勇士").start();
		}
	}
}

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

  1. 有100个限量版的水杯,但是只能通过实体店和官网才能进行购买,并且分别统计卖了多少。请用线程进行模拟并设置线程名称用来代表售出途径,再将信息打印出来。
class Cup implements Runnable{

	int num = 100;
	//初始化一个HashMap对象,销售点为Key,销售量为Value
	Map<String,Integer> map = new HashMap<>();
	
	@Override
	public void run() {
		while(true) {
			synchronized (Cup.class) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (num > 0) {
					String key = Thread.currentThread().getName();
					if (map.containsKey(key)) {
						map.put(key, map.get(key)+1);
					}else {
						map.put(key, 1);
					}
					//累计销售点卖出的数量
					System.out.println(key + "共卖出了" + map.get(key) + "个杯子");
					System.out.println("还剩" + (--num) + "个杯子");
				}else {
					break;
				}
				
			}
		}
	}
	
}

public class Test4 {
	public static void main(String[] args) {
		Cup c = new Cup();
		new Thread(c,"实体店").start();
		new Thread(c,"网店").start();
		
	}
}

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

  1. 有一辆班车除司机外只能承载80个人,假设前中后三个车门都能上车,如果坐满则不能再上车。请用线程模拟上车过程并且在控制台打印出是从哪个车门上车以及剩下的座位数。
class Car implements Runnable{

	int num = 80;
	
	@Override
	public void run() {
		
		while(true) {
			synchronized (Car.class) {
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (num < 1) {
					break;
				}
				System.out.println(Thread.currentThread().getName() + "上车了");
				System.out.println("还剩" + (--num) + "个座位");
			}
		}
	}
}

public class Test5 {
	public static void main(String[] args) {
		Car c = new Car();
		new Thread(c,"前门").start();
		new Thread(c,"中门").start();
		new Thread(c,"后门").start();
	}
}

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

还是没太懂多线程…

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 09:29:41  更:2021-08-06 09:30:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/12 2:27:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码