day13. 多线程

  1. 课前回顾:
  2. Math:数学类
  3. 方法:数学计算
  4. ceil()->向上取整
  5. round()->四舍五入
  6. max()->取较大值
  7. min()->取较小值
  8. abs()->绝对值
  9. BigInteger:
  10. 处理比long型还大的数据
  11. 方法:
  12. add:加法
  13. subtract:减法
  14. multiply:乘法
  15. divide:除法
  16. BigDecimal:
  17. 处理小数,防止精度损失
  18. 方法:
  19. add:加法
  20. subtract:减法
  21. multiply:乘法
  22. divide:除法->如果除不尽会报错
  23. divide(BigDecimal,scala,舍入方式)
  24. scala:保留几位小数
  25. 舍入方式:
  26. 直接舍去
  27. 四舍五入
  28. 向上+1
  29. Date:日期类,精确到毫秒
  30. 构造:
  31. Date():获取当前系统时间
  32. Date(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)
  33. 方法:
  34. setTime(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)
  35. getTime:获取时间毫秒值
  36. Calendar:日历类,抽象类
  37. 获取:getInstance
  38. 方法:
  39. add:给指定的字段设置偏移量
  40. get:获取指定字段的值
  41. set:给指定字段设置值
  42. SimpleDateFormat:日期格式化类
  43. 构造:
  44. SimpleDateFormat("日期格式规则")
  45. y:年 M:月 d:天 H:时 m:分 s:秒
  46. yyyy-MM-dd HH:mm:ss-> 连接符可以随意,但是字母不能随意写
  47. 方法:
  48. String format(Date) 将日期按照指定的格式去转成字符串
  49. Date parse(String s):将符合规则的字符串转成Date对象
  50. jdk8新日期类
  51. LocalDate
  52. 获取:now() of(年,月,日)
  53. get开头的:获取日期字段
  54. with开头的:设置日期
  55. puls:向后偏移
  56. minus:向前偏移
  57. Period:计算日期差值
  58. Duration:计算时间差值
  59. between:计算差值
  60. DateTimeFormatter:日期格式化
  61. Arrays:专门操作数组
  62. sort:排序
  63. toString:按照指定格式打印
  64. binarySearch:二分查找
  65. copyOf:数组扩容->底层依靠System.arrayCopy
  66. System:
  67. exit:退出jvm
  68. currentTimeMills:获取系统时间毫秒值
  69. gc:调用垃圾回收器
  70. arrayCopy:数组赋值
  71. 今日重点:
  72. 1.三种多线程创建方式(继承,实现,匿名内部类)
  73. 2.会使用同步代码块去解决线程安全问题
  74. 3.会使用同步方法去解决线程安全问题
  75. 4.知道什么情况下出现死锁
  76. 5.知道wait方法和sleep方法的区别
  77. 6.完成等待唤醒案例

第一章.多线程基本了解

1.多线程之线程和进程

  1. 进程:进入到内存中运行的应用程序

day13[多线程] - 图1

  1. 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序

day13[多线程] - 图2

并发: 同一个时刻多个线程同时操作了同一个数据

并行: 同一个时刻多个线程同时执行不同的程序

2.CPU调度

  1. 1.分时调度:指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的cpu的时间片
  2. 2.抢占式调度:java程序
  3. 多个线程去抢占CPU使用权,谁先抢到CPU使用权,谁先执行
  4. 优先级高的线程抢到CPU使用权几率大

3.主线程介绍

  1. 1.cpu和内存之间开辟一个专门为main方法服务的通道->主线程
  1. public class Test01 {
  2. public static void main(String[] args) {
  3. for (int i = 0; i < 5; i++) {
  4. System.out.println("哈哈哈哈");
  5. }
  6. System.out.println(Math.abs(-1));
  7. }
  8. }

day13[多线程] - 图3

第二章.创建线程的方式

1.创建线程的第一种方式(extends Thread)(重点)

  1. 1.创建一个类,extends Thread
  2. 2.重写Thread中的run方法->在run中设置线程任务(该线程能干啥)
  3. 3.创建自己定义的线程类对象,调用start方法(开启线程,jvm会自动执行run方法)
  4. 4.start方法和run方法的区别
  5. run()->仅仅是设置线程任务,没有开启线程的功能
  6. start()->先开启线程,然后jvm自动调用run方法,执行线程任务
  1. public class MyThread extends Thread{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 5; i++) {
  5. System.out.println("MyThread线程执行了....."+i);
  6. }
  7. }
  8. }
  1. public class Test02 {
  2. public static void main(String[] args) {
  3. //创建线程类对象
  4. MyThread myThread = new MyThread();
  5. //调用Thread类中的start方法,开启线程,并执行run方法
  6. myThread.start();
  7. //myThread.run();
  8. // ==============================
  9. for (int i = 0; i < 5; i++) {
  10. System.out.println("Main执行了..."+i);
  11. }
  12. }
  13. }

2.多线程在内存中的运行原理

day13[多线程] - 图4

  1. 注意:
  2. 一个线程对象,不能连续start多次
  3. 如果还想创建新的线程,那么重新new一次线程对象,再调用start方法

3.Thread类中的方法

  1. String getName() -> 获取线程名字
  2. static Thread currentThread() -> 获取当前正在执行的线程对象
  3. 在哪个线程中用,获取的就是哪个线程对象
  4. void setName(String name) -> 给线程设置名字
  5. static void sleep(long millis) -> 线程睡眠
  6. millis->设置的是线程睡多长时间->毫秒值
  1. public class MyThread extends Thread{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 5; i++) {
  5. try {
  6. Thread.sleep(1000L);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println(getName()+"...执行了"+i);
  11. }
  12. }
  13. }
  1. public class Test01 {
  2. public static void main(String[] args) {
  3. //创建线程类对象
  4. MyThread myThread = new MyThread();
  5. myThread.setName("尼古拉斯赵四");
  6. //调用Thread类中的start方法,开启线程,并执行run方法
  7. myThread.start();
  8. // ==============================
  9. for (int i = 0; i < 5; i++) {
  10. try {
  11. Thread.sleep(2000L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(Thread.currentThread().getName()+"执行了..."+i);
  16. }
  17. }
  18. }
  1. 问题:在run方法中为什么不能throws异常?
  2. 答案:因为Thread中的run方法没有抛异常,所以重写之后不能throws

4.创建线程的第二种方式(实现Runnable接口)(重点)

  1. 1.定义一个类,实现(implements)Runnable接口
  2. 2.重写Runnable接口中的run方法,设置线程任务
  3. 3.创建自定义类对象,将对象放到Thread对象中
  4. 4.调用Thread类中的start方法开启线程
  5. Thread类中的构造:
  6. Thread(Runnable target)
  7. Thread(Runnable target, String name) -> 根据实现类创建Thread对象,设置线程名字
  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 5; i++) {
  5. System.out.println(Thread.currentThread().getName()+"...执行了"+i);
  6. }
  7. }
  8. }
  1. public class Test01 {
  2. public static void main(String[] args) {
  3. MyRunnable myRunnable = new MyRunnable();
  4. Thread thread = new Thread(myRunnable,"广坤");
  5. thread.start();
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(Thread.currentThread().getName()+"...执行了"+i);
  8. }
  9. }
  10. }

5.两种实现多线程的方式(区别)

  1. 1.方式1:继承Thread方式 -> 单继承,不能多继承,有局限性
  2. 2.方式2:实现Runnable接口 -> 解决了单继承的限制-> 一个类可以继承父类的同时实现一个或者多个接口

6.使用匿名内部类方式创建多线程

  1. 1.匿名内部类回顾:
  2. new 父类/接口(){
  3. 重写方法
  4. }.重写的方法;
  5. 或者:
  6. 父类/接口类型 对象名 = new 父类/接口(){
  7. 重写方法
  8. }
  9. 对象名.重写的方法
  1. public class Test01 {
  2. public static void main(String[] args) {
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println("线程1执行了");
  8. }
  9. }
  10. }).start();
  11. new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. for (int i = 0; i < 5; i++) {
  15. System.out.println("线程2执行了");
  16. }
  17. }
  18. }).start();
  19. }
  20. }

day13[多线程] - 图5

小结:

创建线程三种方式:

  1. 继承Thread类
    a.创建一个类,继承Thread类
    b.重写run方法,设置线程任务
    c.创建线程类对象,调用start方法
  2. 实现Runnable接口
    a.创建一个类,实现Runnable接口
    b.重写run方法,设置线程任务
    c.创建实现类对象,将对象传递到Thread对象中
    d.调用start方法开启线程
  3. 匿名内部类形式:
    new Thread(new Runnable(){
    run(){}
    }).start();

第三章.线程安全

1.线程安全问题的概述

  1. 1.当多个线程访问同一个资源的时候就会出现线程安全问题

2.线程安全问题的代码实现(重点)->有线程安全问题

  1. public class Ticket implements Runnable{
  2. int ticket = 100;
  3. @Override
  4. public void run() {
  5. while(true){
  6. if (ticket>0){
  7. //买票
  8. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  9. ticket--;
  10. }
  11. }
  12. }
  13. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket, "无忌");
  5. Thread t2 = new Thread(ticket, "三丰");
  6. Thread t3 = new Thread(ticket, "翠山");
  7. //开启三个线程
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

3.线程安全问题的产生原理

  1. cpu在多个线程之间做高速切换

4.解决线程安全问题的第一种方式使用同步代码块(重点)

  1. 1.格式:
  2. synchronized(任意对象){
  3. 可能会出现线程安全的代码
  4. }
  5. 2.任意对象->充当的就是锁对象
  6. 3.注意:想要实现线程安全问题,必须要保证是多个线程使用的是同一个锁对象
  1. public class Ticket implements Runnable{
  2. int ticket = 100;
  3. //创建对象
  4. Object obj = new Object();
  5. @Override
  6. public void run() {
  7. while(true){
  8. try {
  9. Thread.sleep(100L);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. synchronized (obj){
  14. if (ticket>0){
  15. //买票
  16. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  17. ticket--;
  18. }
  19. }
  20. }
  21. }
  22. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket, "无忌");
  5. Thread t2 = new Thread(ticket, "三丰");
  6. Thread t3 = new Thread(ticket, "翠山");
  7. //开启三个线程
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

5.同步的原理

day13[多线程] - 图6

进了同步代码块就相当于抢到了锁,其他的线程就抢不到锁;出了同步代码块,相当于释放锁,其他的线程才有资格抢锁

6.解决线程安全问题的第二种方式:使用同步方法(重点)

1.1普通的同步方法

  1. 1.格式:
  2. 修饰符 synchronized 返回值类型 方法名(参数){
  3. 可能出现线程安全的代码
  4. }
  5. 2.默认锁:this
  1. public class Ticket implements Runnable{
  2. int ticket = 100;
  3. //创建对象
  4. Object obj = new Object();
  5. @Override
  6. public void run() {
  7. while(true){
  8. try {
  9. Thread.sleep(100L);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. method();
  14. }
  15. }
  16. //同步方法
  17. public synchronized void method(){
  18. if (ticket>0){
  19. //买票
  20. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  21. ticket--;
  22. }
  23. }
  24. /*public void method(){
  25. synchronized (this){
  26. if (ticket>0){
  27. //买票
  28. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  29. ticket--;
  30. }
  31. }
  32. }*/
  33. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket, "无忌");
  5. Thread t2 = new Thread(ticket, "三丰");
  6. Thread t3 = new Thread(ticket, "翠山");
  7. //开启三个线程
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

1.2.静态同步方法

  1. 1.格式:
  2. 修饰符 static synchronized 返回值类型 方法名(参数){
  3. 可能出现线程安全的代码
  4. }
  5. 2.默认锁:当前类.class
  1. public class Ticket implements Runnable{
  2. static int ticket = 100;
  3. //创建对象
  4. Object obj = new Object();
  5. @Override
  6. public void run() {
  7. while(true){
  8. try {
  9. Thread.sleep(100L);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. method();
  14. }
  15. }
  16. //同步方法
  17. public static synchronized void method(){
  18. if (ticket>0){
  19. //买票
  20. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  21. ticket--;
  22. }
  23. }
  24. /*public static void method(){
  25. synchronized (Ticket.class){
  26. if (ticket>0){
  27. //买票
  28. System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
  29. ticket--;
  30. }
  31. }
  32. }*/
  33. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket, "无忌");
  5. Thread t2 = new Thread(ticket, "三丰");
  6. Thread t3 = new Thread(ticket, "翠山");
  7. //开启三个线程
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

第四章.死锁(了解)

1.死锁介绍(锁嵌套就有可能产生死锁)

  1. 死锁指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.

day13[多线程] - 图7

  1. 根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行;而线程T2正在持有R2锁,但是T2线程需要再次拿到R1锁,才能继续执行.这时两个线程会处于互相等待的状态,即死锁.在程序中的死锁将出现在同步代码块的嵌套中

2.死锁的分析

day13[多线程] - 图8

3.代码实现

day13[多线程] - 图9

  1. public class LockA {
  2. static LockA lockA = new LockA();
  3. }
  4. public class LockB {
  5. static LockB lockB = new LockB();
  6. }
  1. public class DieThread implements Runnable{
  2. private boolean flag;
  3. public DieThread(boolean flag) {
  4. this.flag = flag;
  5. }
  6. @Override
  7. public void run() {
  8. //线程1执行的
  9. if (flag){
  10. synchronized (LockA.lockA){
  11. System.out.println("if...lockA");
  12. synchronized (LockB.lockB){
  13. System.out.println("if...lockB");
  14. }
  15. }
  16. //线程2执行的
  17. }else{
  18. synchronized (LockB.lockB){
  19. System.out.println("else...lockB");
  20. synchronized (LockA.lockA){
  21. System.out.println("else...lockA");
  22. }
  23. }
  24. }
  25. }
  26. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. DieThread dieThread = new DieThread(true);
  4. new Thread(dieThread).start();
  5. DieThread dieThread1 = new DieThread(false);
  6. new Thread(dieThread1).start();
  7. }
  8. }

知道什么情况下会产生死锁即可:锁嵌套

第五章.线程状态

1.线程状态介绍

  1. 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在APIjava.lang.Thread.State这个枚举中给出了六种线程状态:
  2. 这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()

2.线程状态图

day13[多线程] - 图10

第六章.等待唤醒

一.等待唤醒案例分析(线程之间的通信)

day13[多线程] - 图11

二.等待唤醒案例实现

  1. public class BaoZiPu {
  2. //定义count,表示包子
  3. private int count;
  4. //定义flag,证明有么有包子
  5. private boolean flag;
  6. public BaoZiPu() {
  7. }
  8. public BaoZiPu(int count, boolean flag) {
  9. this.count = count;
  10. this.flag = flag;
  11. }
  12. //消费线程要调用的方法,证明消费包子
  13. public void getCount() {
  14. System.out.println("消费了第..."+count+"个包子");
  15. }
  16. //生产者要调用的方法,证明生产包子
  17. public void setCount() {
  18. count++;
  19. System.out.println("生产了第........."+count+"个包子");
  20. }
  21. public boolean isFlag() {
  22. return flag;
  23. }
  24. public void setFlag(boolean flag) {
  25. this.flag = flag;
  26. }
  27. }
  1. //生产者
  2. public class Product implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Product(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. synchronized (baoZiPu){
  16. if (baoZiPu.isFlag()==true){
  17. //证明有包子,生产线程wait
  18. try {
  19. baoZiPu.wait();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. //出了if就要生产包子
  25. baoZiPu.setCount();
  26. //改变flag状态,为true,证明生产完毕,有包子了
  27. baoZiPu.setFlag(true);
  28. //唤醒消费线程
  29. baoZiPu.notify();
  30. }
  31. }
  32. }
  33. }
  1. //消费者
  2. public class Consumer implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Consumer(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. synchronized (baoZiPu){
  16. if (baoZiPu.isFlag()==false){
  17. //证明没有包子,消费线程wait
  18. try {
  19. baoZiPu.wait();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. //出了if就要消费包子
  25. baoZiPu.getCount();
  26. //改变flag状态,为false,证明消费完毕,没有包子了
  27. baoZiPu.setFlag(false);
  28. //唤醒生产线程
  29. baoZiPu.notify();
  30. }
  31. }
  32. }
  33. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. BaoZiPu baoZiPu = new BaoZiPu();
  4. Product product = new Product(baoZiPu);
  5. Consumer consumer = new Consumer(baoZiPu);
  6. new Thread(product).start();
  7. new Thread(consumer).start();
  8. }
  9. }

day13[多线程] - 图12

三.用同步方法改造等待唤醒案例

  1. public class BaoZiPu {
  2. //定义count,表示包子
  3. private int count;
  4. //定义flag,证明有么有包子
  5. private boolean flag;
  6. public BaoZiPu() {
  7. }
  8. public BaoZiPu(int count, boolean flag) {
  9. this.count = count;
  10. this.flag = flag;
  11. }
  12. //消费线程要调用的方法,证明消费包子
  13. public synchronized void getCount() {
  14. if (flag == false) {
  15. //证明没有包子,消费线程wait
  16. try {
  17. this.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. //出了if就要消费包子
  23. System.out.println("消费了第..." + count + "个包子");
  24. //改变flag状态,为false,证明消费完毕,没有包子了
  25. flag = false;
  26. //唤醒生产线程
  27. this.notify();
  28. }
  29. //生产者要调用的方法,证明生产包子
  30. public synchronized void setCount() {
  31. if (flag == true) {
  32. //证明有包子,生产线程wait
  33. try {
  34. this.wait();
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. //出了if就要生产包子
  40. count++;
  41. System.out.println("生产了第........." + count + "个包子");
  42. //改变flag状态,为true,证明生产完毕,有包子了
  43. flag = true;
  44. //唤醒消费线程
  45. this.notify();
  46. }
  47. public boolean isFlag() {
  48. return flag;
  49. }
  50. public void setFlag(boolean flag) {
  51. this.flag = flag;
  52. }
  53. }
  1. //生产者
  2. public class Product implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Product(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //生产
  16. baoZiPu.setCount();
  17. }
  18. }
  19. }
  1. //消费者
  2. public class Consumer implements Runnable{
  3. private BaoZiPu baoZiPu;
  4. public Consumer(BaoZiPu baoZiPu) {
  5. this.baoZiPu = baoZiPu;
  6. }
  7. @Override
  8. public void run() {
  9. while(true){
  10. try {
  11. Thread.sleep(100L);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. //消费
  16. baoZiPu.getCount();
  17. }
  18. }
  19. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. BaoZiPu baoZiPu = new BaoZiPu();
  4. Product product = new Product(baoZiPu);
  5. Consumer consumer = new Consumer(baoZiPu);
  6. new Thread(product).start();
  7. new Thread(consumer).start();
  8. }
  9. }