目录标题
- 一 线程同步(多个线程操作一个资源) 重点难点
- 二 什么是线程同步
- 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不同会释放锁
- 练习二
- 解决消费者生产者模式的问题方法二: 信号灯法 标志位解决
- 十一 线程池
- 总结线程的创建