目录标题
- 一 线程同步(多个线程操作一个资源) 重点难点
- 二 什么是线程同步
- synchronized 线程同步
- 为了保证线程的安全性 当一个线程获得对象的排它锁,独占资源 其他线程必须等待 使用完后才能释放锁即可 但是这么保证线程固然安全 但也缺少了性能问题 出现了以下的问题: 1 一个线程 持有锁会导致其他所有需要此锁的线程来消耗时间挂起 所以锁的存在影响到了效率 降低了性能 2 在多线程的竞争下 加锁 释放锁 会导致比较多的上下文切换和调度延时 引起性能问题 3 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置 引起性能问题(例如一个大号的同学先进入厕所 一个小号的同学在外面等了半个小时 造成性能倒置 应该是小号先进入大号在进入) 所以 安全性提高了 但是性能会降低 性能提高了 安全性会降低!!!重点
- 练习一
- 不安全买票 不安全的买票案例 //可以看出线程不安全 有人拿到了负数的票数 比如黄牛党拿到了第-1票
- 为什么会拿到负的票数 线程不会排队 当剩下最后一张票时 它们同时去抢 出现了负数的结果
- //定义售卖票数停止方式 boolean flag=true;
- @Override public void run() { //买票 while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } }
- //判断是否有票 if (ticketNums<=0){ flag=false; return; }
- 练习二
- 两个人不安全的银行取钱案例
- //创建一个去取钱的对象 //传入的参数意思为:你在结婚基金中取出50元 //传入的参数意思为:妻子在结婚基金中取出100元 Drawing you = new Drawing(account, 50, "你"); Drawing her = new Drawing(account, 100, "妻子");
- //银行:模拟取款 //因为银行不涉及多个线程操控一个对象用继承extends即可 //即多个银行让一个人同时取钱不可能实现 //因为此刻的银行不涉及多个线程操控同一个对象(深度理解) //理解:因为这是一个对象去银行取钱 不涉及多个对象帮助这一个对象去银行取钱 //所以用继承extend更为方便一点 不需要多个线程帮助这一个对象对银行取钱 //参考对比继承Thread类和实现Runnable接口: //继承Thread类时只有一份资源 //实现Runnable接口时一份资源但多个代理 class Drawing extends Thread{}
- //构造器 //在构造器中传入的参数 为 账户 和取多少钱和 哪个对象去取钱 public Drawing(Account account,int drawingMoney,String name){ //复习知识点super //子类super调用父类的构造方法 必须在构造方法的第一行 //在这里调用的时 super(name); //重点: 为什么用super(name);调用父类的参数而不用this.name=name; //this.name=name;//这里可以看到this.name=name;这行代码报错 //为什么呢: 原因 因为Drawing继承了 Thread父类 而父类中的有参构造肯定含有this.name=name; //name这个参数 所以此刻如果再创建那么就会重复 所以就会出现报错的现象 //解决方法 : 调用super();方法 调用父类的name参数来用 this.account=account; this.drawingMoney=drawingMoney; }
- //模拟网络延时 sleep 可以放大问题的发生性 //此刻网络延时的目的是:让两个取钱的对象再此刻同时进入银行取钱 //造成出现负数余额的现象 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
- 练习三
- 线程不安全的集合
- //复习 : Array 数组的工具包 java.util.Arrays //由于数组对象本身没有什么方法可以供我们调用 但API中提供了一个工具类Arrays供我们 //使用 从而可以对数据对象进行一些基本的操作 List list=new ArrayList(); //可以看到通过for循环来创建线程Thread 一共有一万个 for (int i = 0; i < 10000; i++) { //创建线程 new Thread(()->{ //下行代码 将线程的名字添加到了集合中 //Thread.currentThread().getName() 表示当前代码被哪个线程调用 //比如 Thread-0,Thread-1... list.add(Thread.currentThread().getName()); }).start(); }
- 三 Thread.currentThread().getName() 和 this.getName()的区别
- 四 同步方法及同步块
- 五 JUC安全类型的集合
- 六 死锁
- 练习一
- 练习内容 两个女生化妆 都拿着各自对象想要的化妆工具 而都不想进行交换
- 死锁 :多个线程互相抱着对方需要的资源 然后形成僵持
- 练习二改善
- 这里可以看到一条进程里面想要获得两条资源 显然是不行的
- synchronized (lipstick){ //获得口红的锁 System.out.println(this.girlName+"获得口红的锁"); Thread.sleep(1000); } //应该这样理解:代码块外一秒后会释放资源所以这样就解决了 //出现死锁的现象里可以看出 当小明用着口红的时候 口红用完 手里面还拿着口红的的锁 导致其他人不能用 //当小红拿着镜子的时候手里面还拿着 镜子用完 手里还是拿着镜子的锁 导致其他人不能用 //所以对方都拿着对方想要的锁 而不能用 出现了死锁现象 //只要不同时抱着两个锁 //理解为 就是用完口红把锁放下,用完镜子把锁放下,之前是用完了没放下 //将锁拿出来 可以避免出现死锁的现象 //一秒钟后想获得镜子 synchronized (mirror){ System.out.println(this.girlName+"获得镜子的锁"); }
- 七 Lock(锁)
- 八 死锁的避免方法
- 九 synchronized与Lock的对比
- 十 线程协作(生产者消费者模式)
- 线程通信
- 在这里插入图片描述 重点 线程通信的生产者消费者问题中 仅仅有synchronized是远远不够的 因为 synchronized可阻止并发更新同一个共享资源 实现同步 但是synchronized不能用来实现不同线程之间的消息传递(通信)
- 重点 这里可以看到 要想解决生产者消费者问题 可以使用wait 方法 notify方法 跟sleep方法不同 sleep方法是抱着锁进入睡眠状态 而wait方法可以释放资源
- 练习一(重点)
- 解决生产者与消费者的模式 方法一管程法
- notify();方法 唤醒一个处于等待状态的线程 wait();方法 如果线程一直等待 直到其他线程通知 与sleep不同会释放锁
- 练习二
- 解决消费者生产者模式的问题方法二: 信号灯法 标志位解决
- 十一 线程池
- 总结线程的创建
一 线程同步(多个线程操作一个资源) 重点难点

现实生活中 排队的名字 在代码中叫做队列

处理多线程问题时 多个线程访问同一个对象这种情况叫做并发
这个时候我们就需要线程同步 线程同步其实就是一种等待机制(排队)

队列的话 类似于 食堂大妈给同学打饭必须要排队才能安全快速的进行
锁机制的话 就类似于 大家一起上厕所 一个人进去厕所后 会锁上门 否则的话 后面的人就会进去造成不应该的矛盾和麻烦
所以为了提高安全性 形成的条件就是 队列加锁
二 什么是线程同步

synchronized 线程同步
为了保证线程的安全性 当一个线程获得对象的排它锁,独占资源 其他线程必须等待 使用完后才能释放锁即可
但是这么保证线程固然安全 但也缺少了性能问题
出现了以下的问题:
1 一个线程 持有锁会导致其他所有需要此锁的线程来消耗时间挂起
所以锁的存在影响到了效率 降低了性能
2 在多线程的竞争下 加锁 释放锁 会导致比较多的上下文切换和调度延时 引起性能问题
3 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置 引起性能问题(例如一个大号的同学先进入厕所 一个小号的同学在外面等了半个小时 造成性能倒置 应该是小号先进入大号在进入)
所以 安全性提高了 但是性能会降低
性能提高了 安全性会降低!!!重点
练习一
不安全买票
不安全的买票案例
//可以看出线程不安全 有人拿到了负数的票数 比如黄牛党拿到了第-1票
为什么会拿到负的票数
线程不会排队 当剩下最后一张票时 它们同时去抢 出现了负数的结果
//定义售卖票数停止方式
boolean flag=true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断是否有票
if (ticketNums<=0){
flag=false;
return;
}

练习二
两个人不安全的银行取钱案例
//创建一个去取钱的对象
//传入的参数意思为:你在结婚基金中取出50元
//传入的参数意思为:妻子在结婚基金中取出100元
Drawing you = new Drawing(account, 50, “你”);
Drawing her = new Drawing(account, 100, “妻子”);
//银行:模拟取款
//因为银行不涉及多个线程操控一个对象用继承extends即可
//即多个银行让一个人同时取钱不可能实现
//因为此刻的银行不涉及多个线程操控同一个对象(深度理解)
//理解:因为这是一个对象去银行取钱 不涉及多个对象帮助这一个对象去银行取钱
//所以用继承extend更为方便一点 不需要多个线程帮助这一个对象对银行取钱
//参考对比继承Thread类和实现Runnable接口:
//继承Thread类时只有一份资源
//实现Runnable接口时一份资源但多个代理
class Drawing extends Thread{}
//构造器
//在构造器中传入的参数 为 账户 和取多少钱和 哪个对象去取钱
public Drawing(Account account,int drawingMoney,String name){
//复习知识点super
//子类super调用父类的构造方法 必须在构造方法的第一行
//在这里调用的时
super(name);
//重点: 为什么用super(name);调用父类的参数而不用this.name=name;
//this.name=name;//这里可以看到this.name=name;这行代码报错
//为什么呢: 原因 因为Drawing继承了 Thread父类 而父类中的有参构造肯定含有this.name=name;
//name这个参数 所以此刻如果再创建那么就会重复 所以就会出现报错的现象
//解决方法 : 调用super();方法 调用父类的name参数来用
this.account=account;
this.drawingMoney=drawingMoney;
}
//模拟网络延时 sleep 可以放大问题的发生性
//此刻网络延时的目的是:让两个取钱的对象再此刻同时进入银行取钱
//造成出现负数余额的现象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

练习三
线程不安全的集合
//复习 : Array 数组的工具包 java.util.Arrays
//由于数组对象本身没有什么方法可以供我们调用 但API中提供了一个工具类Arrays供我们
//使用 从而可以对数据对象进行一些基本的操作
List list=new ArrayList();
//可以看到通过for循环来创建线程Thread 一共有一万个
for (int i = 0; i < 10000; i++) {
//创建线程
new Thread(()->{
//下行代码 将线程的名字添加到了集合中
//Thread.currentThread().getName() 表示当前代码被哪个线程调用
//比如 Thread-0,Thread-1…
list.add(Thread.currentThread().getName());
}).start();
}

三 Thread.currentThread().getName() 和 this.getName()的区别
Thread.curentThread().getName() 表示当前的代码被被哪个线程所调用
this.getName() 表示当前线程(当前这个这段代码属于哪个线程里面的)
//isAlive() 方法 测试线程是否处于活动状态

四 同步方法及同步块

synchronized同步方法的原理是 控制对“对象”的访问 每一个对象都有一把锁 每一个synchronized方法都必须获得调用该方法的对象的锁才能执行 否则线程就会阻塞


练习一改善

练习二改善

练习三改善

五 JUC安全类型的集合
测试JUC安全类型的集合

六 死锁

练习一
练习内容
两个女生化妆 都拿着各自对象想要的化妆工具
而都不想进行交换
死锁 :多个线程互相抱着对方需要的资源 然后形成僵持

练习二改善
这里可以看到一条进程里面想要获得两条资源
显然是不行的
synchronized (lipstick){
//获得口红的锁
System.out.println(this.girlName+“获得口红的锁”);
Thread.sleep(1000);
}
//应该这样理解:代码块外一秒后会释放资源所以这样就解决了
//出现死锁的现象里可以看出 当小明用着口红的时候 口红用完 手里面还拿着口红的的锁 导致其他人不能用
//当小红拿着镜子的时候手里面还拿着 镜子用完 手里还是拿着镜子的锁 导致其他人不能用
//所以对方都拿着对方想要的锁 而不能用 出现了死锁现象
//只要不同时抱着两个锁
//理解为 就是用完口红把锁放下,用完镜子把锁放下,之前是用完了没放下
//将锁拿出来 可以避免出现死锁的现象
//一秒钟后想获得镜子
synchronized (mirror){
System.out.println(this.girlName+“获得镜子的锁”);
}

七 Lock(锁)
从JDK5.0开始的 提供了更强大的线程同步机制
ReentrantLock 可重入锁

八 死锁的避免方法


练习一
买票的案例
//定义lock锁
private final ReentrantLock lock=new ReentrantLock();

九 synchronized与Lock的对比

十 线程协作(生产者消费者模式)
线程通信


重点
线程通信的生产者消费者问题中 仅仅有synchronized是远远不够的
因为 synchronized可阻止并发更新同一个共享资源 实现同步
但是synchronized不能用来实现不同线程之间的消息传递(通信)

重点
这里可以看到 要想解决生产者消费者问题 可以使用wait 方法 notify方法 跟sleep方法不同 sleep方法是抱着锁进入睡眠状态
而wait方法可以释放资源


练习一(重点)
解决生产者与消费者的模式
方法一管程法
notify();方法 唤醒一个处于等待状态的线程
wait();方法 如果线程一直等待 直到其他线程通知 与sleep不同会释放锁


练习二
解决消费者生产者模式的问题方法二:
信号灯法 标志位解决

十一 线程池


线程池创建
通过ExecutorService线程池接口 来创建一个自定义大小的线程池
如果创建线程是实现的Runnable接口 ze 用execute调用 执行线程
但是execute没有返回值
但是如果创建线程是实现的Callable接口 则用 submit来调用执行线程 而submit有返回值
上面两种方法创建完线程池后最后都分别用shutdown()方法关闭连接池

总结线程的创建
回顾多线程创建的方法

本文详细介绍了Java多线程中的线程同步概念,包括队列和锁机制,强调了同步带来的安全性与性能下降的权衡。通过不安全的买票、银行取款和线程不安全的集合等实例,解释了线程不安全的问题,并探讨了解决方案。此外,文章还讨论了Thread.currentThread().getName()和this.getName()的区别,介绍了死锁的概念和避免方法,以及Lock(如ReentrantLock)在提高线程安全性和性能上的优势。最后,文章提到了线程通信的重要性,展示了使用wait和notify解决生产者消费者问题的方法,并简单概述了线程池的创建和使用。
460

被折叠的 条评论
为什么被折叠?



